From 92f68213162df099ecabc10e7d10173e322db27a Mon Sep 17 00:00:00 2001 From: Swangi Kumari <85639103+swangi-kumari@users.noreply.github.com> Date: Tue, 15 Apr 2025 12:37:26 +0530 Subject: [PATCH] feat(core): Add support for updating metadata after payment has been authorized (#7776) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .../payments/payments--update-metadata.mdx | 3 + api-reference/mint.json | 3 +- api-reference/openapi_spec.json | 69 +++++ crates/api_models/src/events/payment.rs | 20 +- crates/api_models/src/payments.rs | 22 ++ crates/common_enums/src/enums.rs | 8 +- .../src/connectors/paypal.rs | 6 +- .../src/default_implementations.rs | 112 ++++++- .../src/default_implementations_v2.rs | 22 +- .../hyperswitch_domain_models/src/payments.rs | 27 ++ .../src/router_data.rs | 16 +- .../src/router_flow_types/payments.rs | 3 + .../src/router_request_types.rs | 6 + .../src/router_response_types.rs | 4 +- crates/hyperswitch_domain_models/src/types.rs | 8 +- .../src/api/payments.rs | 11 +- .../src/api/payments_v2.rs | 16 +- crates/hyperswitch_interfaces/src/types.rs | 12 +- crates/openapi/src/openapi.rs | 3 + crates/openapi/src/routes/payments.rs | 15 + crates/router/src/connector/stripe.rs | 95 ++++++ .../src/connector/stripe/transformers.rs | 24 ++ crates/router/src/core/payments.rs | 3 +- crates/router/src/core/payments/flows.rs | 41 +++ .../payments/flows/update_metadata_flow.rs | 147 +++++++++ crates/router/src/core/payments/operations.rs | 3 + .../payments/operations/payment_response.rs | 106 ++++++- .../operations/payment_update_metadata.rs | 285 ++++++++++++++++++ .../router/src/core/payments/transformers.rs | 68 +++++ crates/router/src/routes/app.rs | 4 + crates/router/src/routes/lock_utils.rs | 1 + crates/router/src/routes/payments.rs | 76 +++++ crates/router/src/services/api.rs | 1 + crates/router/src/types.rs | 20 +- crates/router/src/types/api/payments.rs | 17 +- crates/router/src/types/api/payments_v2.rs | 5 +- crates/router/tests/connectors/utils.rs | 4 +- crates/router_derive/src/macros/operation.rs | 12 +- crates/router_env/src/logger/types.rs | 2 + 39 files changed, 1241 insertions(+), 59 deletions(-) create mode 100644 api-reference/api-reference/payments/payments--update-metadata.mdx create mode 100644 crates/router/src/core/payments/flows/update_metadata_flow.rs create mode 100644 crates/router/src/core/payments/operations/payment_update_metadata.rs diff --git a/api-reference/api-reference/payments/payments--update-metadata.mdx b/api-reference/api-reference/payments/payments--update-metadata.mdx new file mode 100644 index 0000000000..e65662a306 --- /dev/null +++ b/api-reference/api-reference/payments/payments--update-metadata.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /payments/{payment_id}/update_metadata +--- \ No newline at end of file diff --git a/api-reference/mint.json b/api-reference/mint.json index 077b111eaa..ac6996705a 100644 --- a/api-reference/mint.json +++ b/api-reference/mint.json @@ -69,7 +69,8 @@ "api-reference/payments/payments-link--retrieve", "api-reference/payments/payments--list", "api-reference/payments/payments--external-3ds-authentication", - "api-reference/payments/payments--complete-authorize" + "api-reference/payments/payments--complete-authorize", + "api-reference/payments/payments--update-metadata" ] }, { diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index b414d6f426..dafeaf65c0 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -950,6 +950,45 @@ ] } }, + "/payments/{payment_id}/update_metadata": { + "post": { + "tags": [ + "Payments" + ], + "summary": "Payments - Update Metadata", + "operationId": "Update Metadata for a Payment", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsUpdateMetadataRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Metadata updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsUpdateMetadataResponse" + } + } + } + }, + "400": { + "description": "Missing mandatory fields" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, "/relay": { "post": { "tags": [ @@ -21644,6 +21683,36 @@ } } }, + "PaymentsUpdateMetadataRequest": { + "type": "object", + "required": [ + "metadata" + ], + "properties": { + "metadata": { + "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object." + } + }, + "additionalProperties": false + }, + "PaymentsUpdateMetadataResponse": { + "type": "object", + "required": [ + "payment_id" + ], + "properties": { + "payment_id": { + "type": "string", + "description": "The identifier for the payment" + }, + "metadata": { + "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object.", + "nullable": true + } + } + }, "PaymentsUpdateRequest": { "type": "object", "properties": { diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index 044b548b98..a6b2c53d28 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -32,7 +32,7 @@ use crate::{ PaymentsManualUpdateRequest, PaymentsManualUpdateResponse, PaymentsPostSessionTokensRequest, PaymentsPostSessionTokensResponse, PaymentsRejectRequest, PaymentsRetrieveRequest, PaymentsSessionResponse, PaymentsStartRequest, - RedirectionResponse, + PaymentsUpdateMetadataRequest, PaymentsUpdateMetadataResponse, RedirectionResponse, }, }; @@ -93,6 +93,24 @@ impl ApiEventMetric for PaymentsPostSessionTokensRequest { } } +#[cfg(feature = "v1")] +impl ApiEventMetric for PaymentsUpdateMetadataRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.payment_id.clone(), + }) + } +} + +#[cfg(feature = "v1")] +impl ApiEventMetric for PaymentsUpdateMetadataResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.payment_id.clone(), + }) + } +} + #[cfg(feature = "v1")] impl ApiEventMetric for PaymentsPostSessionTokensResponse { fn get_api_event_type(&self) -> Option { diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index ad11cc06b9..2d32ee6593 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -6442,6 +6442,28 @@ pub struct PaymentsSessionRequest { pub merchant_connector_details: Option, } +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct PaymentsUpdateMetadataRequest { + /// The unique identifier for the payment + #[serde(skip_deserializing)] + #[schema(value_type = String)] + pub payment_id: id_type::PaymentId, + /// Metadata is useful for storing additional, unstructured information on an object. + #[schema(value_type = Object, example = r#"{ "udf1": "some-value", "udf2": "some-value" }"#)] + pub metadata: pii::SecretSerdeValue, +} + +#[derive(Debug, serde::Serialize, Clone, ToSchema)] +pub struct PaymentsUpdateMetadataResponse { + /// The identifier for the payment + #[schema(value_type = String)] + pub payment_id: id_type::PaymentId, + /// 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, +} + #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] pub struct PaymentsPostSessionTokensRequest { /// The unique identifier for the payment diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 7780e710f3..cb711c5251 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -334,11 +334,17 @@ pub enum AuthorizationStatus { #[router_derive::diesel_enum(storage_type = "text")] #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] -pub enum SessionUpdateStatus { +pub enum PaymentResourceUpdateStatus { Success, Failure, } +impl PaymentResourceUpdateStatus { + pub fn is_success(&self) -> bool { + matches!(self, Self::Success) + } +} + #[derive( Clone, Debug, diff --git a/crates/hyperswitch_connectors/src/connectors/paypal.rs b/crates/hyperswitch_connectors/src/connectors/paypal.rs index 02d2b0e7ae..189c012839 100644 --- a/crates/hyperswitch_connectors/src/connectors/paypal.rs +++ b/crates/hyperswitch_connectors/src/connectors/paypal.rs @@ -948,12 +948,12 @@ impl ConnectorIntegration { + $( impl PaymentUpdateMetadata for $path::$connector {} + impl + ConnectorIntegration< + UpdateMetadata, + PaymentsUpdateMetadataData, + PaymentsResponseData, + > for $path::$connector + {} + )* + }; +} + +default_imp_for_update_metadata!( + connectors::Aci, + connectors::Adyen, + connectors::Airwallex, + connectors::Amazonpay, + connectors::Authorizedotnet, + connectors::Bambora, + connectors::Bamboraapac, + connectors::Bankofamerica, + connectors::Bitpay, + connectors::Bluesnap, + connectors::Braintree, + connectors::Boku, + connectors::Billwerk, + connectors::Cashtocode, + connectors::Chargebee, + connectors::Checkout, + connectors::Coinbase, + connectors::Coingate, + connectors::Cryptopay, + connectors::Cybersource, + connectors::Datatrans, + connectors::Digitalvirgo, + connectors::Dlocal, + connectors::Elavon, + connectors::Facilitapay, + connectors::Fiserv, + connectors::Fiservemea, + connectors::Forte, + connectors::Getnet, + connectors::Helcim, + connectors::Iatapay, + connectors::Inespay, + connectors::Itaubank, + connectors::Jpmorgan, + connectors::Juspaythreedsserver, + connectors::Klarna, + connectors::Paypal, + connectors::Rapyd, + connectors::Razorpay, + connectors::Recurly, + connectors::Redsys, + connectors::Shift4, + connectors::Square, + connectors::Stax, + connectors::Stripebilling, + connectors::Taxjar, + connectors::Mifinity, + connectors::Mollie, + connectors::Moneris, + connectors::Multisafepay, + connectors::Nomupay, + connectors::Noon, + connectors::Novalnet, + connectors::Nexinets, + connectors::Nexixpay, + connectors::Opayo, + connectors::Opennode, + connectors::Nuvei, + connectors::Paybox, + connectors::Payeezy, + connectors::Payme, + connectors::Paystack, + connectors::Payu, + connectors::Placetopay, + connectors::Fiuu, + connectors::Globalpay, + connectors::Globepay, + connectors::Gocardless, + connectors::Hipay, + connectors::Worldline, + connectors::Worldpay, + connectors::Wellsfargo, + connectors::Xendit, + connectors::Powertranz, + connectors::Prophetpay, + connectors::Thunes, + connectors::Trustpay, + connectors::Tsys, + connectors::UnifiedAuthenticationService, + connectors::Deutschebank, + connectors::Volt, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard +); + use crate::connectors; macro_rules! default_imp_for_complete_authorize { ($($path:ident::$connector:ident),*) => { diff --git a/crates/hyperswitch_connectors/src/default_implementations_v2.rs b/crates/hyperswitch_connectors/src/default_implementations_v2.rs index dc7e8c3a11..41817d955a 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -19,7 +19,7 @@ use hyperswitch_domain_models::{ Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, PSync, PaymentMethodToken, PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, - SetupMandate, Void, + SetupMandate, UpdateMetadata, Void, }, refunds::{Execute, RSync}, revenue_recovery::{BillingConnectorPaymentsSync, RecoveryRecordBack}, @@ -35,9 +35,10 @@ use hyperswitch_domain_models::{ PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, - PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, RefundsData, - RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, - SubmitEvidenceRequestData, UploadFileRequestData, VerifyWebhookSourceRequestData, + PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, + PaymentsUpdateMetadataData, RefundsData, RetrieveFileRequestData, + SdkPaymentsSessionUpdateData, SetupMandateRequestData, SubmitEvidenceRequestData, + UploadFileRequestData, VerifyWebhookSourceRequestData, }, router_response_types::{ revenue_recovery::{ @@ -91,8 +92,9 @@ use hyperswitch_interfaces::{ ConnectorCustomerV2, MandateSetupV2, PaymentApproveV2, PaymentAuthorizeSessionTokenV2, PaymentAuthorizeV2, PaymentCaptureV2, PaymentIncrementalAuthorizationV2, PaymentPostSessionTokensV2, PaymentRejectV2, PaymentSessionUpdateV2, PaymentSessionV2, - PaymentSyncV2, PaymentTokenV2, PaymentV2, PaymentVoidV2, PaymentsCompleteAuthorizeV2, - PaymentsPostProcessingV2, PaymentsPreProcessingV2, TaxCalculationV2, + PaymentSyncV2, PaymentTokenV2, PaymentUpdateMetadataV2, PaymentV2, PaymentVoidV2, + PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2, + TaxCalculationV2, }, refunds_v2::{RefundExecuteV2, RefundSyncV2, RefundV2}, revenue_recovery_v2::{ @@ -128,6 +130,7 @@ macro_rules! default_imp_for_new_connector_integration_payment { impl TaxCalculationV2 for $path::$connector{} impl PaymentSessionUpdateV2 for $path::$connector{} impl PaymentPostSessionTokensV2 for $path::$connector{} + impl PaymentUpdateMetadataV2 for $path::$connector{} impl ConnectorIntegrationV2 for $path::$connector{} @@ -219,6 +222,13 @@ macro_rules! default_imp_for_new_connector_integration_payment { PaymentsPostSessionTokensData, PaymentsResponseData, > for $path::$connector{} + impl + ConnectorIntegrationV2< + UpdateMetadata, + PaymentFlowData, + PaymentsUpdateMetadataData, + PaymentsResponseData, + > for $path::$connector{} )* }; } diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 3c6a253d4b..02a1622712 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -225,6 +225,33 @@ impl PaymentIntent { .map(|metadata| metadata.parse_value(type_name)) .transpose() } + + #[cfg(feature = "v1")] + pub fn merge_metadata( + &self, + request_metadata: Value, + ) -> Result { + if !request_metadata.is_null() { + match (&self.metadata, &request_metadata) { + (Some(Value::Object(existing_map)), Value::Object(req_map)) => { + let mut merged = existing_map.clone(); + merged.extend(req_map.clone()); + Ok(Value::Object(merged)) + } + (None, Value::Object(_)) => Ok(request_metadata), + _ => { + router_env::logger::error!( + "Expected metadata to be an object, got: {:?}", + request_metadata + ); + Err(common_utils::errors::ParsingError::UnknownError) + } + } + } else { + router_env::logger::error!("Metadata does not contain any key"); + Err(common_utils::errors::ParsingError::UnknownError) + } + } } #[cfg(feature = "v2")] diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index 73c387d97c..d3f9a2bf8c 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -624,7 +624,9 @@ impl router_response_types::PaymentsResponseData::PostProcessingResponse { .. } => { todo!() } - router_response_types::PaymentsResponseData::SessionUpdateResponse { .. } => { + router_response_types::PaymentsResponseData::PaymentResourceUpdateResponse { + .. + } => { todo!() } }, @@ -834,7 +836,9 @@ impl router_response_types::PaymentsResponseData::PostProcessingResponse { .. } => { todo!() } - router_response_types::PaymentsResponseData::SessionUpdateResponse { .. } => { + router_response_types::PaymentsResponseData::PaymentResourceUpdateResponse { + .. + } => { todo!() } }, @@ -1062,7 +1066,9 @@ impl router_response_types::PaymentsResponseData::PostProcessingResponse { .. } => { todo!() } - router_response_types::PaymentsResponseData::SessionUpdateResponse { .. } => { + router_response_types::PaymentsResponseData::PaymentResourceUpdateResponse { + .. + } => { todo!() } }, @@ -1318,7 +1324,9 @@ impl router_response_types::PaymentsResponseData::PostProcessingResponse { .. } => { todo!() } - router_response_types::PaymentsResponseData::SessionUpdateResponse { .. } => { + router_response_types::PaymentsResponseData::PaymentResourceUpdateResponse { + .. + } => { todo!() } }, 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 fbc797269d..5cf47bf62a 100644 --- a/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs +++ b/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs @@ -70,3 +70,6 @@ pub struct PostSessionTokens; #[derive(Debug, Clone)] pub struct RecordAttempt; + +#[derive(Debug, Clone)] +pub struct UpdateMetadata; diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index ab90cd2efb..7a91f82f30 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -94,6 +94,12 @@ pub struct PaymentsPostSessionTokensData { pub router_return_url: Option, } +#[derive(Debug, Clone)] +pub struct PaymentsUpdateMetadataData { + pub metadata: pii::SecretSerdeValue, + pub connector_transaction_id: String, +} + #[derive(Debug, Clone, PartialEq)] pub struct AuthoriseIntegrityObject { /// Authorise amount diff --git a/crates/hyperswitch_domain_models/src/router_response_types.rs b/crates/hyperswitch_domain_models/src/router_response_types.rs index 3a09d6a6f7..bfba6ad553 100644 --- a/crates/hyperswitch_domain_models/src/router_response_types.rs +++ b/crates/hyperswitch_domain_models/src/router_response_types.rs @@ -72,8 +72,8 @@ pub enum PaymentsResponseData { PostProcessingResponse { session_token: Option, }, - SessionUpdateResponse { - status: common_enums::SessionUpdateStatus, + PaymentResourceUpdateResponse { + status: common_enums::PaymentResourceUpdateStatus, }, } diff --git a/crates/hyperswitch_domain_models/src/types.rs b/crates/hyperswitch_domain_models/src/types.rs index 691fde0de9..8b7ae7f750 100644 --- a/crates/hyperswitch_domain_models/src/types.rs +++ b/crates/hyperswitch_domain_models/src/types.rs @@ -8,7 +8,7 @@ use crate::{ BillingConnectorPaymentsSync, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, Execute, IncrementalAuthorization, PSync, PaymentMethodToken, PostAuthenticate, PostSessionTokens, PreAuthenticate, PreProcessing, RSync, - SdkSessionUpdate, Session, SetupMandate, VerifyWebhookSource, Void, + SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, VerifyWebhookSource, Void, }, router_request_types::{ revenue_recovery::{BillingConnectorPaymentsSyncRequest, RevenueRecoveryRecordBackRequest}, @@ -22,8 +22,8 @@ use crate::{ PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsSessionData, PaymentsSyncData, - PaymentsTaxCalculationData, RefundsData, SdkPaymentsSessionUpdateData, - SetupMandateRequestData, VerifyWebhookSourceRequestData, + PaymentsTaxCalculationData, PaymentsUpdateMetadataData, RefundsData, + SdkPaymentsSessionUpdateData, SetupMandateRequestData, VerifyWebhookSourceRequestData, }, router_response_types::{ revenue_recovery::{ @@ -62,6 +62,8 @@ pub type RefreshTokenRouterData = RouterData; pub type PaymentsSessionRouterData = RouterData; +pub type PaymentsUpdateMetadataRouterData = + RouterData; pub type UasPostAuthenticationRouterData = RouterData; diff --git a/crates/hyperswitch_interfaces/src/api/payments.rs b/crates/hyperswitch_interfaces/src/api/payments.rs index ab327d0629..01e7d2aedf 100644 --- a/crates/hyperswitch_interfaces/src/api/payments.rs +++ b/crates/hyperswitch_interfaces/src/api/payments.rs @@ -5,7 +5,7 @@ use hyperswitch_domain_models::{ Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, PSync, PaymentMethodToken, PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, - SetupMandate, Void, + SetupMandate, UpdateMetadata, Void, }, router_request_types::{ AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, @@ -13,7 +13,7 @@ use hyperswitch_domain_models::{ PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, - SdkPaymentsSessionUpdateData, SetupMandateRequestData, + PaymentsUpdateMetadataData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, }, router_response_types::{PaymentsResponseData, TaxCalculationResponseData}, }; @@ -42,6 +42,7 @@ pub trait Payment: + PaymentIncrementalAuthorization + PaymentSessionUpdate + PaymentPostSessionTokens + + PaymentUpdateMetadata { } @@ -133,6 +134,12 @@ pub trait PaymentPostSessionTokens: { } +/// trait UpdateMetadata +pub trait PaymentUpdateMetadata: + api::ConnectorIntegration +{ +} + /// trait PaymentsCompleteAuthorize pub trait PaymentsCompleteAuthorize: api::ConnectorIntegration diff --git a/crates/hyperswitch_interfaces/src/api/payments_v2.rs b/crates/hyperswitch_interfaces/src/api/payments_v2.rs index 366024f7da..f60925435e 100644 --- a/crates/hyperswitch_interfaces/src/api/payments_v2.rs +++ b/crates/hyperswitch_interfaces/src/api/payments_v2.rs @@ -6,7 +6,7 @@ use hyperswitch_domain_models::{ Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, PSync, PaymentMethodToken, PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, - SetupMandate, Void, + SetupMandate, UpdateMetadata, Void, }, router_request_types::{ AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, @@ -14,7 +14,7 @@ use hyperswitch_domain_models::{ PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, - SdkPaymentsSessionUpdateData, SetupMandateRequestData, + PaymentsUpdateMetadataData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, }, router_response_types::{PaymentsResponseData, TaxCalculationResponseData}, }; @@ -126,6 +126,17 @@ pub trait PaymentPostSessionTokensV2: { } +/// trait PaymentUpdateMetadataV2 +pub trait PaymentUpdateMetadataV2: + ConnectorIntegrationV2< + UpdateMetadata, + PaymentFlowData, + PaymentsUpdateMetadataData, + PaymentsResponseData, +> +{ +} + /// trait PaymentsCompleteAuthorizeV2 pub trait PaymentsCompleteAuthorizeV2: ConnectorIntegrationV2< @@ -204,5 +215,6 @@ pub trait PaymentV2: + TaxCalculationV2 + PaymentSessionUpdateV2 + PaymentPostSessionTokensV2 + + PaymentUpdateMetadataV2 { } diff --git a/crates/hyperswitch_interfaces/src/types.rs b/crates/hyperswitch_interfaces/src/types.rs index 8e33d5bc15..b85ce4732a 100644 --- a/crates/hyperswitch_interfaces/src/types.rs +++ b/crates/hyperswitch_interfaces/src/types.rs @@ -11,7 +11,7 @@ use hyperswitch_domain_models::{ Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, InitPayment, PSync, PaymentMethodToken, PostProcessing, PostSessionTokens, PreProcessing, SdkSessionUpdate, - Session, SetupMandate, Void, + Session, SetupMandate, UpdateMetadata, Void, }, refunds::{Execute, RSync}, revenue_recovery::{BillingConnectorPaymentsSync, RecoveryRecordBack}, @@ -32,9 +32,10 @@ use hyperswitch_domain_models::{ MandateRevokeRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, - PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, RefundsData, - RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, - SubmitEvidenceRequestData, UploadFileRequestData, VerifyWebhookSourceRequestData, + PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, + PaymentsUpdateMetadataData, RefundsData, RetrieveFileRequestData, + SdkPaymentsSessionUpdateData, SetupMandateRequestData, SubmitEvidenceRequestData, + UploadFileRequestData, VerifyWebhookSourceRequestData, }, router_response_types::{ revenue_recovery::{ @@ -79,6 +80,9 @@ pub type PaymentsPostSessionTokensType = dyn ConnectorIntegration< PaymentsPostSessionTokensData, PaymentsResponseData, >; +/// Type alias for `ConnectorIntegration` +pub type PaymentsUpdateMetadataType = + dyn ConnectorIntegration; /// Type alias for `ConnectorIntegration` pub type SdkSessionUpdateType = dyn ConnectorIntegration; diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index dc3850b573..5d9ccfab2a 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -83,6 +83,7 @@ Never share your secret api keys. Keep them guarded and secure. routes::payments::payments_external_authentication, routes::payments::payments_complete_authorize, routes::payments::payments_post_session_tokens, + routes::payments::payments_update_metadata, // Routes for relay routes::relay::relay, @@ -730,6 +731,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::DisplayAmountOnSdk, api_models::payments::PaymentsPostSessionTokensRequest, api_models::payments::PaymentsPostSessionTokensResponse, + api_models::payments::PaymentsUpdateMetadataRequest, + api_models::payments::PaymentsUpdateMetadataResponse, api_models::payments::CtpServiceDetails, api_models::feature_matrix::FeatureMatrixListResponse, api_models::feature_matrix::FeatureMatrixRequest, diff --git a/crates/openapi/src/routes/payments.rs b/crates/openapi/src/routes/payments.rs index aa6d666fec..63a60238d2 100644 --- a/crates/openapi/src/routes/payments.rs +++ b/crates/openapi/src/routes/payments.rs @@ -599,6 +599,21 @@ pub fn payments_dynamic_tax_calculation() {} pub fn payments_post_session_tokens() {} +/// Payments - Update Metadata +#[utoipa::path( + post, + path = "/payments/{payment_id}/update_metadata", + request_body=PaymentsUpdateMetadataRequest, + responses( + (status = 200, description = "Metadata updated successfully", body = PaymentsUpdateMetadataResponse), + (status = 400, description = "Missing mandatory fields") + ), + tag = "Payments", + operation_id = "Update Metadata for a Payment", + security(("api_key" = [])) +)] +pub fn payments_update_metadata() {} + /// Payments - Create Intent /// /// **Creates a payment intent object when amount_details are passed.** diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index 704b01246f..0fcddc5c34 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -177,6 +177,7 @@ impl ConnectorValidation for Stripe { impl api::Payment for Stripe {} impl api::PaymentAuthorize for Stripe {} +impl api::PaymentUpdateMetadata for Stripe {} impl api::PaymentSync for Stripe {} impl api::PaymentVoid for Stripe {} impl api::PaymentCapture for Stripe {} @@ -971,6 +972,100 @@ impl } } +impl + services::ConnectorIntegration< + api::UpdateMetadata, + types::PaymentsUpdateMetadataData, + types::PaymentsResponseData, + > for Stripe +{ + fn get_headers( + &self, + req: &types::RouterData< + api::UpdateMetadata, + types::PaymentsUpdateMetadataData, + types::PaymentsResponseData, + >, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + "application/x-www-form-urlencoded" + } + + fn get_url( + &self, + req: &types::PaymentsUpdateMetadataRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + let payment_id = &req.request.connector_transaction_id; + Ok(format!( + "{}v1/payment_intents/{}", + self.base_url(connectors), + payment_id + )) + } + + fn get_request_body( + &self, + req: &types::PaymentsUpdateMetadataRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_req = stripe::UpdateMetadataRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &types::PaymentsUpdateMetadataRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsUpdateMetadataType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsUpdateMetadataType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsUpdateMetadataType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &types::PaymentsUpdateMetadataRouterData, + _event_builder: Option<&mut ConnectorEvent>, + res: types::Response, + ) -> CustomResult { + router_env::logger::debug!("skipped parsing of the response"); + // If 200 status code, then metadata was updated successfully. + let status = if res.status_code == 200 { + enums::PaymentResourceUpdateStatus::Success + } else { + enums::PaymentResourceUpdateStatus::Failure + }; + Ok(types::PaymentsUpdateMetadataRouterData { + response: Ok(types::PaymentsResponseData::PaymentResourceUpdateResponse { status }), + ..data.clone() + }) + } + + fn get_error_response( + &self, + res: types::Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + impl services::ConnectorIntegration< api::Void, diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 576bb2976c..9d372a5415 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -3385,6 +3385,30 @@ impl TryFrom<&types::PaymentsCancelRouterData> for CancelRequest { } } +#[derive(Debug, Serialize)] +pub struct UpdateMetadataRequest { + #[serde(flatten)] + pub metadata: HashMap, +} + +impl TryFrom<&types::PaymentsUpdateMetadataRouterData> for UpdateMetadataRequest { + type Error = error_stack::Report; + fn try_from(item: &types::PaymentsUpdateMetadataRouterData) -> Result { + let metadata = format_metadata_for_request(item.request.metadata.clone()); + Ok(Self { metadata }) + } +} + +fn format_metadata_for_request(merchant_metadata: Secret) -> HashMap { + let mut formatted_metadata = HashMap::new(); + if let Value::Object(metadata_map) = merchant_metadata.expose() { + for (key, value) in metadata_map { + formatted_metadata.insert(format!("metadata[{}]", key), value.to_string()); + } + } + formatted_metadata +} + #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] #[non_exhaustive] #[serde(rename_all = "snake_case")] diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index a81e88bf95..0bf98966f3 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -73,7 +73,7 @@ use time; pub use self::operations::{ PaymentApprove, PaymentCancel, PaymentCapture, PaymentConfirm, PaymentCreate, PaymentIncrementalAuthorization, PaymentPostSessionTokens, PaymentReject, PaymentSession, - PaymentSessionUpdate, PaymentStatus, PaymentUpdate, + PaymentSessionUpdate, PaymentStatus, PaymentUpdate, PaymentUpdateMetadata, }; use self::{ conditional_configs::perform_decision_management, @@ -5547,6 +5547,7 @@ where "PaymentSession" => true, "PaymentSessionUpdate" => true, "PaymentPostSessionTokens" => true, + "PaymentUpdateMetadata" => true, "PaymentIncrementalAuthorization" => matches!( payment_data.get_payment_intent().status, storage_enums::IntentStatus::RequiresCapture diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 1ac0913b22..afee6a42b1 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -10,6 +10,7 @@ pub mod reject_flow; pub mod session_flow; pub mod session_update_flow; pub mod setup_mandate_flow; +pub mod update_metadata_flow; use async_trait::async_trait; #[cfg(all(feature = "v2", feature = "revenue_recovery"))] @@ -1700,6 +1701,46 @@ default_imp_for_post_session_tokens!( connector::Wise ); +macro_rules! default_imp_for_update_metadata { + ($($path:ident::$connector:ident),*) => { + $( impl api::PaymentUpdateMetadata for $path::$connector {} + impl + services::ConnectorIntegration< + api::UpdateMetadata, + types::PaymentsUpdateMetadataData, + types::PaymentsResponseData + > for $path::$connector + {} + )* + }; +} +#[cfg(feature = "dummy_connector")] +impl api::PaymentUpdateMetadata for connector::DummyConnector {} +#[cfg(feature = "dummy_connector")] +impl + services::ConnectorIntegration< + api::UpdateMetadata, + types::PaymentsUpdateMetadataData, + types::PaymentsResponseData, + > for connector::DummyConnector +{ +} + +default_imp_for_update_metadata!( + connector::Adyenplatform, + connector::Ebanx, + connector::Gpayments, + connector::Netcetera, + connector::Nmi, + connector::Payone, + connector::Plaid, + connector::Riskified, + connector::Signifyd, + connector::Threedsecureio, + connector::Wellsfargopayout, + connector::Wise +); + macro_rules! default_imp_for_uas_pre_authentication { ($($path:ident::$connector:ident),*) => { $( impl UnifiedAuthenticationService for $path::$connector {} diff --git a/crates/router/src/core/payments/flows/update_metadata_flow.rs b/crates/router/src/core/payments/flows/update_metadata_flow.rs new file mode 100644 index 0000000000..113791f4d6 --- /dev/null +++ b/crates/router/src/core/payments/flows/update_metadata_flow.rs @@ -0,0 +1,147 @@ +use async_trait::async_trait; + +use super::ConstructFlowSpecificData; +use crate::{ + core::{ + errors::{ConnectorErrorExt, RouterResult}, + payments::{self, access_token, helpers, transformers, Feature, PaymentData}, + }, + routes::SessionState, + services, + types::{self, api, domain}, +}; + +#[async_trait] +impl + ConstructFlowSpecificData< + api::UpdateMetadata, + types::PaymentsUpdateMetadataData, + types::PaymentsResponseData, + > for PaymentData +{ + #[cfg(feature = "v1")] + async fn construct_router_data<'a>( + &self, + state: &SessionState, + connector_id: &str, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + customer: &Option, + merchant_connector_account: &helpers::MerchantConnectorAccountType, + merchant_recipient_data: Option, + header_payload: Option, + ) -> RouterResult { + Box::pin(transformers::construct_payment_router_data::< + api::UpdateMetadata, + types::PaymentsUpdateMetadataData, + >( + state, + self.clone(), + connector_id, + merchant_account, + key_store, + customer, + merchant_connector_account, + merchant_recipient_data, + header_payload, + )) + .await + } + + #[cfg(feature = "v2")] + async fn construct_router_data<'a>( + &self, + state: &SessionState, + connector_id: &str, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + customer: &Option, + merchant_connector_account: &domain::MerchantConnectorAccount, + merchant_recipient_data: Option, + header_payload: Option, + ) -> RouterResult { + todo!() + } + + async fn get_merchant_recipient_data<'a>( + &self, + _state: &SessionState, + _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, + _merchant_connector_account: &helpers::MerchantConnectorAccountType, + _connector: &api::ConnectorData, + ) -> RouterResult> { + Ok(None) + } +} + +#[async_trait] +impl Feature + for types::RouterData< + api::UpdateMetadata, + types::PaymentsUpdateMetadataData, + types::PaymentsResponseData, + > +{ + async fn decide_flows<'a>( + self, + state: &SessionState, + connector: &api::ConnectorData, + call_connector_action: payments::CallConnectorAction, + connector_request: Option, + _business_profile: &domain::Profile, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, + ) -> RouterResult { + let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< + api::UpdateMetadata, + types::PaymentsUpdateMetadataData, + types::PaymentsResponseData, + > = connector.connector.get_connector_integration(); + + let resp = services::execute_connector_processing_step( + state, + connector_integration, + &self, + call_connector_action, + connector_request, + ) + .await + .to_payment_failed_response()?; + Ok(resp) + } + + async fn add_access_token<'a>( + &self, + state: &SessionState, + connector: &api::ConnectorData, + merchant_account: &domain::MerchantAccount, + creds_identifier: Option<&str>, + ) -> RouterResult { + access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) + .await + } + + async fn build_flow_specific_connector_request( + &mut self, + state: &SessionState, + connector: &api::ConnectorData, + call_connector_action: payments::CallConnectorAction, + ) -> RouterResult<(Option, bool)> { + let request = match call_connector_action { + payments::CallConnectorAction::Trigger => { + let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< + api::UpdateMetadata, + types::PaymentsUpdateMetadataData, + types::PaymentsResponseData, + > = connector.connector.get_connector_integration(); + + connector_integration + .build_request(self, &state.conf.connectors) + .to_payment_failed_response()? + } + _ => None, + }; + + Ok((request, true)) + } +} diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index d914e59253..764b66c4c1 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -26,6 +26,8 @@ pub mod payment_status; #[cfg(feature = "v1")] pub mod payment_update; #[cfg(feature = "v1")] +pub mod payment_update_metadata; +#[cfg(feature = "v1")] pub mod payments_incremental_authorization; #[cfg(feature = "v1")] pub mod tax_calculation; @@ -71,6 +73,7 @@ pub use self::{ payment_create::PaymentCreate, payment_post_session_tokens::PaymentPostSessionTokens, payment_reject::PaymentReject, payment_session::PaymentSession, payment_start::PaymentStart, payment_status::PaymentStatus, payment_update::PaymentUpdate, + payment_update_metadata::PaymentUpdateMetadata, payments_incremental_authorization::PaymentIncrementalAuthorization, tax_calculation::PaymentSessionUpdate, }; diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 2d3ad48667..f5f95288ee 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -4,7 +4,7 @@ use api_models::payments::{ConnectorMandateReferenceId, MandateReferenceId}; #[cfg(feature = "dynamic_routing")] use api_models::routing::RoutableConnectorChoice; use async_trait::async_trait; -use common_enums::{AuthorizationStatus, SessionUpdateStatus}; +use common_enums::AuthorizationStatus; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] use common_utils::ext_traits::ValueExt; use common_utils::{ @@ -61,7 +61,7 @@ use crate::{ #[derive(Debug, Clone, Copy, router_derive::PaymentOperation)] #[operation( operations = "post_update_tracker", - flow = "sync_data, cancel_data, authorize_data, capture_data, complete_authorize_data, approve_data, reject_data, setup_mandate_data, session_data,incremental_authorization_data, sdk_session_update_data, post_session_tokens_data" + flow = "sync_data, cancel_data, authorize_data, capture_data, complete_authorize_data, approve_data, reject_data, setup_mandate_data, session_data,incremental_authorization_data, sdk_session_update_data, post_session_tokens_data, update_metadata_data" )] pub struct PaymentResponse; @@ -702,8 +702,8 @@ impl PostUpdateTracker, types::SdkPaymentsSessionUpd // 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() { - Ok(types::PaymentsResponseData::SessionUpdateResponse { status }) => { - if status == SessionUpdateStatus::Success { + Ok(types::PaymentsResponseData::PaymentResourceUpdateResponse { status }) => { + if status.is_success() { let shipping_address = payment_data .tax_data .clone() @@ -852,6 +852,100 @@ impl PostUpdateTracker, types::PaymentsPostSessionTo } } +#[cfg(feature = "v1")] +#[async_trait] +impl PostUpdateTracker, types::PaymentsUpdateMetadataData> + for PaymentResponse +{ + async fn update_tracker<'b>( + &'b self, + db: &'b SessionState, + mut payment_data: PaymentData, + router_data: types::RouterData< + F, + types::PaymentsUpdateMetadataData, + types::PaymentsResponseData, + >, + key_store: &domain::MerchantKeyStore, + storage_scheme: enums::MerchantStorageScheme, + _locale: &Option, + #[cfg(all(feature = "v1", feature = "dynamic_routing"))] _routable_connector: Vec< + RoutableConnectorChoice, + >, + #[cfg(all(feature = "v1", feature = "dynamic_routing"))] + _business_profile: &domain::Profile, + ) -> RouterResult> + where + F: 'b + Send, + { + let connector = payment_data + .payment_attempt + .connector + .clone() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("connector not found in payment_attempt")?; + + match router_data.response.clone() { + Ok(types::PaymentsResponseData::PaymentResourceUpdateResponse { status, .. }) => { + if status.is_success() { + let m_db = db.clone().store; + let payment_intent = payment_data.payment_intent.clone(); + let key_manager_state: KeyManagerState = db.into(); + + let payment_intent_update = + hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::MetadataUpdate { + metadata: payment_data + .payment_intent + .metadata + .clone() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("payment_intent.metadata not found")?, + updated_by: payment_data.payment_intent.updated_by.clone(), + }; + + let updated_payment_intent = m_db + .update_payment_intent( + &key_manager_state, + payment_intent, + payment_intent_update, + key_store, + storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + payment_data.payment_intent = updated_payment_intent; + } else { + router_data.response.map_err(|err| { + errors::ApiErrorResponse::ExternalConnectorError { + code: err.code, + message: err.message, + connector, + status_code: err.status_code, + reason: err.reason, + } + })?; + } + } + Err(err) => { + Err(errors::ApiErrorResponse::ExternalConnectorError { + code: err.code, + message: err.message, + connector, + status_code: err.status_code, + reason: err.reason, + })?; + } + _ => { + Err(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unexpected response in Update Metadata flow")?; + } + } + + Ok(payment_data) + } +} + #[cfg(feature = "v1")] #[async_trait] impl PostUpdateTracker, types::PaymentsCaptureData> @@ -1794,7 +1888,9 @@ async fn payment_response_update_tracker( types::PaymentsResponseData::IncrementalAuthorizationResponse { .. } => (None, None), - types::PaymentsResponseData::SessionUpdateResponse { .. } => (None, None), + types::PaymentsResponseData::PaymentResourceUpdateResponse { .. } => { + (None, None) + } types::PaymentsResponseData::MultipleCaptureResponse { capture_sync_response_list, } => match payment_data.multiple_capture_data { diff --git a/crates/router/src/core/payments/operations/payment_update_metadata.rs b/crates/router/src/core/payments/operations/payment_update_metadata.rs new file mode 100644 index 0000000000..b9e372bcbb --- /dev/null +++ b/crates/router/src/core/payments/operations/payment_update_metadata.rs @@ -0,0 +1,285 @@ +use std::marker::PhantomData; + +use api_models::enums::FrmSuggestion; +use async_trait::async_trait; +use common_utils::types::keymanager::KeyManagerState; +use error_stack::ResultExt; +use masking::ExposeInterface; +use router_derive::PaymentOperation; +use router_env::{instrument, tracing}; + +use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest}; +use crate::{ + core::{ + errors::{self, RouterResult, StorageErrorExt}, + payments::{self, helpers, operations, PaymentData}, + }, + routes::{app::ReqState, SessionState}, + services, + types::{ + api::{self, PaymentIdTypeExt}, + domain, + storage::{self, enums as storage_enums}, + }, + utils::OptionExt, +}; + +#[derive(Debug, Clone, Copy, PaymentOperation)] +#[operation(operations = "all", flow = "update_metadata")] +pub struct PaymentUpdateMetadata; + +type PaymentUpdateMetadataOperation<'b, F> = + BoxedOperation<'b, F, api::PaymentsUpdateMetadataRequest, PaymentData>; + +#[async_trait] +impl GetTracker, api::PaymentsUpdateMetadataRequest> + for PaymentUpdateMetadata +{ + #[instrument(skip_all)] + async fn get_trackers<'a>( + &'a self, + state: &'a SessionState, + payment_id: &api::PaymentIdType, + request: &api::PaymentsUpdateMetadataRequest, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + _auth_flow: services::AuthFlow, + _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, + ) -> RouterResult< + operations::GetTrackerResponse<'a, F, api::PaymentsUpdateMetadataRequest, PaymentData>, + > { + let payment_id = payment_id + .get_payment_intent_id() + .change_context(errors::ApiErrorResponse::PaymentNotFound)?; + + let db = &*state.store; + let key_manager_state: &KeyManagerState = &state.into(); + let merchant_id = merchant_account.get_id(); + let storage_scheme = merchant_account.storage_scheme; + let mut payment_intent = db + .find_payment_intent_by_payment_id_merchant_id( + &state.into(), + &payment_id, + merchant_id, + key_store, + storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + helpers::validate_payment_status_against_allowed_statuses( + payment_intent.status, + &[ + storage_enums::IntentStatus::Succeeded, + storage_enums::IntentStatus::Failed, + storage_enums::IntentStatus::PartiallyCaptured, + storage_enums::IntentStatus::PartiallyCapturedAndCapturable, + storage_enums::IntentStatus::RequiresCapture, + ], + "update_metadata", + )?; + + let payment_attempt = db + .find_payment_attempt_by_payment_id_merchant_id_attempt_id( + &payment_intent.payment_id, + merchant_id, + payment_intent.active_attempt.get_id().as_str(), + storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + let currency = payment_intent.currency.get_required_value("currency")?; + let amount = payment_attempt.get_total_amount().into(); + let profile_id = payment_intent + .profile_id + .as_ref() + .get_required_value("profile_id") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("'profile_id' not set in payment intent")?; + + let business_profile = db + .find_business_profile_by_profile_id(key_manager_state, key_store, profile_id) + .await + .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound { + id: profile_id.get_string_repr().to_owned(), + })?; + + let merged_metadata = payment_intent + .merge_metadata(request.metadata.clone().expose()) + .change_context(errors::ApiErrorResponse::InvalidRequestData { + message: "Metadata should be an object and contain at least 1 key".to_owned(), + })?; + + payment_intent.metadata = Some(merged_metadata); + + let payment_data = PaymentData { + flow: PhantomData, + payment_intent, + payment_attempt, + currency, + amount, + email: None, + mandate_id: None, + mandate_connector: None, + customer_acceptance: None, + token: None, + token_data: None, + setup_mandate: None, + address: payments::PaymentAddress::new(None, None, None, None), + confirm: None, + payment_method_data: None, + payment_method_info: None, + force_sync: None, + refunds: vec![], + disputes: vec![], + attempts: None, + sessions_token: vec![], + card_cvc: None, + creds_identifier: None, + pm_token: None, + connector_customer_id: None, + recurring_mandate_payment_data: None, + ephemeral_key: None, + multiple_capture_data: None, + redirect_response: None, + surcharge_details: None, + frm_message: None, + payment_link_data: None, + incremental_authorization_details: None, + authorizations: vec![], + authentication: None, + recurring_details: None, + poll_config: None, + tax_data: None, + session_id: None, + service_details: None, + card_testing_guard_data: None, + vault_operation: None, + threeds_method_comp_ind: None, + }; + let get_trackers_response = operations::GetTrackerResponse { + operation: Box::new(self), + customer_details: None, + payment_data, + business_profile, + mandate_type: None, + }; + + Ok(get_trackers_response) + } +} + +#[async_trait] +impl Domain> + for PaymentUpdateMetadata +{ + #[instrument(skip_all)] + async fn get_or_create_customer_details<'a>( + &'a self, + _state: &SessionState, + _payment_data: &mut PaymentData, + _request: Option, + _merchant_key_store: &domain::MerchantKeyStore, + _storage_scheme: storage_enums::MerchantStorageScheme, + ) -> errors::CustomResult< + ( + PaymentUpdateMetadataOperation<'a, F>, + Option, + ), + errors::StorageError, + > { + Ok((Box::new(self), None)) + } + + #[instrument(skip_all)] + async fn make_pm_data<'a>( + &'a self, + _state: &'a SessionState, + _payment_data: &mut PaymentData, + _storage_scheme: storage_enums::MerchantStorageScheme, + _merchant_key_store: &domain::MerchantKeyStore, + _customer: &Option, + _business_profile: &domain::Profile, + _should_retry_with_pan: bool, + ) -> RouterResult<( + PaymentUpdateMetadataOperation<'a, F>, + Option, + Option, + )> { + Ok((Box::new(self), None, None)) + } + + async fn get_connector<'a>( + &'a self, + _merchant_account: &domain::MerchantAccount, + state: &SessionState, + _request: &api::PaymentsUpdateMetadataRequest, + _payment_intent: &storage::PaymentIntent, + _merchant_key_store: &domain::MerchantKeyStore, + ) -> errors::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 PaymentData, + ) -> errors::CustomResult { + Ok(false) + } +} + +#[async_trait] +impl UpdateTracker, api::PaymentsUpdateMetadataRequest> + for PaymentUpdateMetadata +{ + #[instrument(skip_all)] + async fn update_trackers<'b>( + &'b self, + _state: &'b SessionState, + _req_state: ReqState, + payment_data: PaymentData, + _customer: Option, + _storage_scheme: storage_enums::MerchantStorageScheme, + _updated_customer: Option, + _key_store: &domain::MerchantKeyStore, + _frm_suggestion: Option, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, + ) -> RouterResult<(PaymentUpdateMetadataOperation<'b, F>, PaymentData)> + where + F: 'b + Send, + { + Ok((Box::new(self), payment_data)) + } +} + +impl ValidateRequest> + for PaymentUpdateMetadata +{ + #[instrument(skip_all)] + fn validate_request<'a, 'b>( + &'b self, + request: &api::PaymentsUpdateMetadataRequest, + merchant_account: &'a domain::MerchantAccount, + ) -> RouterResult<( + PaymentUpdateMetadataOperation<'b, F>, + operations::ValidateResult, + )> { + //payment id is already generated and should be sent in the request + let given_payment_id = request.payment_id.clone(); + + Ok(( + Box::new(self), + operations::ValidateResult { + merchant_id: merchant_account.get_id().to_owned(), + payment_id: api::PaymentIdType::PaymentIntentId(given_payment_id), + storage_scheme: merchant_account.storage_scheme, + requeue: false, + }, + )) + } +} diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 077a288c16..04102bcfb0 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1896,6 +1896,38 @@ where } } +#[cfg(feature = "v1")] +impl ToResponse for api::PaymentsUpdateMetadataResponse +where + F: Clone, + Op: Debug, + D: OperationSessionGetters, +{ + 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 { + Ok(services::ApplicationResponse::JsonWithHeaders(( + Self { + payment_id: payment_data.get_payment_intent().payment_id.clone(), + metadata: payment_data + .get_payment_intent() + .metadata + .clone() + .map(Secret::new), + }, + vec![], + ))) + } +} + impl ForeignTryFrom<(MinorUnit, Option, Option, Currency)> for api_models::payments::DisplayAmountOnSdk { @@ -3713,6 +3745,42 @@ impl TryFrom> for types::PaymentsPostSess } } +#[cfg(feature = "v2")] +impl TryFrom> for types::PaymentsUpdateMetadataData { + type Error = error_stack::Report; + + fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { + todo!() + } +} + +#[cfg(feature = "v1")] +impl TryFrom> for types::PaymentsUpdateMetadataData { + type Error = error_stack::Report; + + fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { + let payment_data = additional_data.payment_data.clone(); + let connector = api::ConnectorData::get_connector_by_name( + &additional_data.state.conf.connectors, + &additional_data.connector_name, + api::GetToken::Connector, + payment_data.payment_attempt.merchant_connector_id.clone(), + )?; + Ok(Self { + metadata: payment_data + .payment_intent + .metadata + .map(Secret::new) + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("payment_intent.metadata not found")?, + connector_transaction_id: connector + .connector + .connector_transaction_id(payment_data.payment_attempt.clone())? + .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?, + }) + } +} + impl TryFrom> for types::PaymentsRejectData { type Error = error_stack::Report; diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index f6386a9e2c..321d1e04dd 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -777,6 +777,10 @@ impl Payments { .service( web::resource("{payment_id}/calculate_tax") .route(web::post().to(payments::payments_dynamic_tax_calculation)), + ) + .service( + web::resource("{payment_id}/update_metadata") + .route(web::post().to(payments::payments_update_metadata)), ); } route diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 028051aa81..760a4bc890 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -150,6 +150,7 @@ impl From for ApiIdentifier { | Flow::PaymentsCreateIntent | Flow::PaymentsGetIntent | Flow::PaymentsPostSessionTokens + | Flow::PaymentsUpdateMetadata | Flow::PaymentsUpdateIntent | Flow::PaymentsCreateAndConfirmIntent | Flow::PaymentStartRedirection diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 2ebd384bb8..337c6f6517 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -633,6 +633,65 @@ pub async fn payments_post_session_tokens( .await } +#[cfg(feature = "v1")] +#[instrument(skip_all, fields(flow = ?Flow::PaymentsUpdateMetadata, payment_id))] +pub async fn payments_update_metadata( + state: web::Data, + req: actix_web::HttpRequest, + json_payload: web::Json, + path: web::Path, +) -> impl Responder { + let flow = Flow::PaymentsUpdateMetadata; + + let payment_id = path.into_inner(); + let payload = payment_types::PaymentsUpdateMetadataRequest { + payment_id, + ..json_payload.into_inner() + }; + tracing::Span::current().record("payment_id", payload.payment_id.get_string_repr()); + let header_payload = match HeaderPayload::foreign_try_from(req.headers()) { + Ok(headers) => headers, + Err(err) => { + return api::log_and_return_error_response(err); + } + }; + + let locking_action = payload.get_locking_input(flow.clone()); + + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth, req, req_state| { + payments::payments_core::< + api_types::UpdateMetadata, + payment_types::PaymentsUpdateMetadataResponse, + _, + _, + _, + payments::PaymentData, + >( + state, + req_state, + auth.merchant_account, + auth.profile_id, + auth.key_store, + payments::PaymentUpdateMetadata, + req, + api::AuthFlow::Client, + payments::CallConnectorAction::Trigger, + None, + header_payload.clone(), + None, + ) + }, + &auth::HeaderAuth(auth::ApiKeyAuth), + locking_action, + )) + .await +} + #[cfg(feature = "v1")] #[instrument(skip_all, fields(flow = ?Flow::PaymentsConfirm, payment_id))] pub async fn payments_confirm( @@ -2124,6 +2183,23 @@ impl GetLockingInput for payment_types::PaymentsPostSessionTokensRequest { } } +#[cfg(feature = "v1")] +impl GetLockingInput for payment_types::PaymentsUpdateMetadataRequest { + fn get_locking_input(&self, flow: F) -> api_locking::LockAction + where + F: types::FlowMetric, + lock_utils::ApiIdentifier: From, + { + api_locking::LockAction::Hold { + input: api_locking::LockingInput { + unique_locking_key: self.payment_id.get_string_repr().to_owned(), + api_identifier: lock_utils::ApiIdentifier::from(flow), + override_lock_retries: None, + }, + } + } +} + #[cfg(feature = "v1")] impl GetLockingInput for payments::PaymentsRedirectResponseData { fn get_locking_input(&self, flow: F) -> api_locking::LockAction diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 04656d108c..5f3ce598ab 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -1270,6 +1270,7 @@ impl Authenticate for api_models::payments::PaymentsPostSessionTokensRequest { } } +impl Authenticate for api_models::payments::PaymentsUpdateMetadataRequest {} impl Authenticate for api_models::payments::PaymentsRetrieveRequest {} impl Authenticate for api_models::payments::PaymentsCancelRequest {} impl Authenticate for api_models::payments::PaymentsCaptureRequest {} diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 2902479244..2927bc4c8f 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -38,7 +38,7 @@ use hyperswitch_domain_models::router_flow_types::{ Approve, Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, InitPayment, PSync, PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, - SetupMandate, Void, + SetupMandate, UpdateMetadata, Void, }, refunds::{Execute, RSync}, webhooks::VerifyWebhookSource, @@ -71,9 +71,10 @@ pub use hyperswitch_domain_models::{ PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, - PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, RefundsData, ResponseId, - RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, - SplitRefundsRequest, SubmitEvidenceRequestData, SyncRequestType, UploadFileRequestData, + PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, + PaymentsUpdateMetadataData, RefundsData, ResponseId, RetrieveFileRequestData, + SdkPaymentsSessionUpdateData, SetupMandateRequestData, SplitRefundsRequest, + SubmitEvidenceRequestData, SyncRequestType, UploadFileRequestData, VerifyWebhookSourceRequestData, }, router_response_types::{ @@ -97,9 +98,10 @@ pub use hyperswitch_interfaces::types::{ MandateRevokeType, PaymentsAuthorizeType, PaymentsBalanceType, PaymentsCaptureType, PaymentsCompleteAuthorizeType, PaymentsInitType, PaymentsPostProcessingType, PaymentsPostSessionTokensType, PaymentsPreAuthorizeType, PaymentsPreProcessingType, - PaymentsSessionType, PaymentsSyncType, PaymentsVoidType, RefreshTokenType, RefundExecuteType, - RefundSyncType, Response, RetrieveFileType, SdkSessionUpdateType, SetupMandateType, - SubmitEvidenceType, TokenizationType, UploadFileType, VerifyWebhookSourceType, + PaymentsSessionType, PaymentsSyncType, PaymentsUpdateMetadataType, PaymentsVoidType, + RefreshTokenType, RefundExecuteType, RefundSyncType, Response, RetrieveFileType, + SdkSessionUpdateType, SetupMandateType, SubmitEvidenceType, TokenizationType, UploadFileType, + VerifyWebhookSourceType, }; #[cfg(feature = "payouts")] pub use hyperswitch_interfaces::types::{ @@ -153,6 +155,9 @@ pub type SdkSessionUpdateRouterData = pub type PaymentsPostSessionTokensRouterData = RouterData; +pub type PaymentsUpdateMetadataRouterData = + RouterData; + pub type PaymentsCancelRouterData = RouterData; pub type PaymentsRejectRouterData = RouterData; pub type PaymentsApproveRouterData = RouterData; @@ -408,6 +413,7 @@ impl Capturable for SetupMandateRequestData {} impl Capturable for PaymentsTaxCalculationData {} impl Capturable for SdkPaymentsSessionUpdateData {} impl Capturable for PaymentsPostSessionTokensData {} +impl Capturable for PaymentsUpdateMetadataData {} impl Capturable for PaymentsCancelData { fn get_captured_amount(&self, payment_data: &PaymentData) -> Option where diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index 9f509f3bcb..325956acb8 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -27,8 +27,9 @@ pub use api_models::{ PaymentsPostSessionTokensResponse, PaymentsRedirectRequest, PaymentsRedirectionResponse, PaymentsRejectRequest, PaymentsRequest, PaymentsResponse, PaymentsResponseForm, PaymentsRetrieveRequest, PaymentsSessionRequest, PaymentsSessionResponse, - PaymentsStartRequest, PgRedirectResponse, PhoneDetails, RedirectionResponse, SessionToken, - UrlDetails, VerifyRequest, VerifyResponse, WalletData, + PaymentsStartRequest, PaymentsUpdateMetadataRequest, PaymentsUpdateMetadataResponse, + PgRedirectResponse, PhoneDetails, RedirectionResponse, SessionToken, UrlDetails, + VerifyRequest, VerifyResponse, WalletData, }, }; use error_stack::ResultExt; @@ -36,22 +37,24 @@ pub use hyperswitch_domain_models::router_flow_types::payments::{ Approve, Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, InitPayment, PSync, PaymentCreateIntent, PaymentGetIntent, PaymentMethodToken, PaymentUpdateIntent, PostProcessing, PostSessionTokens, - PreProcessing, RecordAttempt, Reject, SdkSessionUpdate, Session, SetupMandate, Void, + PreProcessing, RecordAttempt, Reject, SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, + Void, }; pub use hyperswitch_interfaces::api::payments::{ ConnectorCustomer, MandateSetup, Payment, PaymentApprove, PaymentAuthorize, PaymentAuthorizeSessionToken, PaymentCapture, PaymentIncrementalAuthorization, PaymentPostSessionTokens, PaymentReject, PaymentSession, PaymentSessionUpdate, PaymentSync, - PaymentToken, PaymentVoid, PaymentsCompleteAuthorize, PaymentsPostProcessing, - PaymentsPreProcessing, TaxCalculation, + PaymentToken, PaymentUpdateMetadata, PaymentVoid, PaymentsCompleteAuthorize, + PaymentsPostProcessing, PaymentsPreProcessing, TaxCalculation, }; pub use super::payments_v2::{ ConnectorCustomerV2, MandateSetupV2, PaymentApproveV2, PaymentAuthorizeSessionTokenV2, PaymentAuthorizeV2, PaymentCaptureV2, PaymentIncrementalAuthorizationV2, PaymentPostSessionTokensV2, PaymentRejectV2, PaymentSessionUpdateV2, PaymentSessionV2, - PaymentSyncV2, PaymentTokenV2, PaymentV2, PaymentVoidV2, PaymentsCompleteAuthorizeV2, - PaymentsPostProcessingV2, PaymentsPreProcessingV2, TaxCalculationV2, + PaymentSyncV2, PaymentTokenV2, PaymentUpdateMetadataV2, PaymentV2, PaymentVoidV2, + PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2, + TaxCalculationV2, }; use crate::core::errors; diff --git a/crates/router/src/types/api/payments_v2.rs b/crates/router/src/types/api/payments_v2.rs index b28cfcf281..78e3ea7a70 100644 --- a/crates/router/src/types/api/payments_v2.rs +++ b/crates/router/src/types/api/payments_v2.rs @@ -2,6 +2,7 @@ pub use hyperswitch_interfaces::api::payments_v2::{ ConnectorCustomerV2, MandateSetupV2, PaymentApproveV2, PaymentAuthorizeSessionTokenV2, PaymentAuthorizeV2, PaymentCaptureV2, PaymentIncrementalAuthorizationV2, PaymentPostSessionTokensV2, PaymentRejectV2, PaymentSessionUpdateV2, PaymentSessionV2, - PaymentSyncV2, PaymentTokenV2, PaymentV2, PaymentVoidV2, PaymentsCompleteAuthorizeV2, - PaymentsPostProcessingV2, PaymentsPreProcessingV2, TaxCalculationV2, + PaymentSyncV2, PaymentTokenV2, PaymentUpdateMetadataV2, PaymentV2, PaymentVoidV2, + PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2, + TaxCalculationV2, }; diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index c315200257..6a7239b5b4 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -576,7 +576,7 @@ pub trait ConnectorActions: Connector { Ok(types::PaymentsResponseData::MultipleCaptureResponse { .. }) => None, Ok(types::PaymentsResponseData::IncrementalAuthorizationResponse { .. }) => None, Ok(types::PaymentsResponseData::PostProcessingResponse { .. }) => None, - Ok(types::PaymentsResponseData::SessionUpdateResponse { .. }) => None, + Ok(types::PaymentsResponseData::PaymentResourceUpdateResponse { .. }) => None, Err(_) => None, } } @@ -1128,7 +1128,7 @@ pub fn get_connector_transaction_id( Ok(types::PaymentsResponseData::MultipleCaptureResponse { .. }) => None, Ok(types::PaymentsResponseData::IncrementalAuthorizationResponse { .. }) => None, Ok(types::PaymentsResponseData::PostProcessingResponse { .. }) => None, - Ok(types::PaymentsResponseData::SessionUpdateResponse { .. }) => None, + Ok(types::PaymentsResponseData::PaymentResourceUpdateResponse { .. }) => None, Err(_) => None, } } diff --git a/crates/router_derive/src/macros/operation.rs b/crates/router_derive/src/macros/operation.rs index 995db7a6ce..103f83076b 100644 --- a/crates/router_derive/src/macros/operation.rs +++ b/crates/router_derive/src/macros/operation.rs @@ -33,6 +33,8 @@ pub enum Derives { SdkSessionUpdateData, PostSessionTokens, PostSessionTokensData, + UpdateMetadata, + UpdateMetadataData, } impl Derives { @@ -121,6 +123,12 @@ impl Conversion { Derives::PostSessionTokensData => { syn::Ident::new("PaymentsPostSessionTokensData", Span::call_site()) } + Derives::UpdateMetadata => { + syn::Ident::new("PaymentsUpdateMetadataRequest", Span::call_site()) + } + Derives::UpdateMetadataData => { + syn::Ident::new("PaymentsUpdateMetadataData", Span::call_site()) + } } } @@ -443,6 +451,7 @@ pub fn operation_derive_inner(input: DeriveInput) -> syn::Result syn::Result