mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 01:57:45 +08:00 
			
		
		
		
	feat(webhooks): adyen - consume and update connector's network_transaction_id in payment_methods (#6738)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
		| @ -7,7 +7,7 @@ use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; | |||||||
|     any(feature = "v1", feature = "v2"), |     any(feature = "v1", feature = "v2"), | ||||||
|     not(feature = "payment_methods_v2") |     not(feature = "payment_methods_v2") | ||||||
| ))] | ))] | ||||||
| use masking::Secret; | use masking::{ExposeInterface, Secret}; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| use time::PrimitiveDateTime; | use time::PrimitiveDateTime; | ||||||
|  |  | ||||||
| @ -253,6 +253,10 @@ pub enum PaymentMethodUpdate { | |||||||
|         network_token_locker_id: Option<String>, |         network_token_locker_id: Option<String>, | ||||||
|         network_token_payment_method_data: Option<Encryption>, |         network_token_payment_method_data: Option<Encryption>, | ||||||
|     }, |     }, | ||||||
|  |     ConnectorNetworkTransactionIdAndMandateDetailsUpdate { | ||||||
|  |         connector_mandate_details: Option<pii::SecretSerdeValue>, | ||||||
|  |         network_transaction_id: Option<Secret<String>>, | ||||||
|  |     }, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] | #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] | ||||||
| @ -646,6 +650,27 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal { | |||||||
|                 network_token_locker_id, |                 network_token_locker_id, | ||||||
|                 network_token_payment_method_data, |                 network_token_payment_method_data, | ||||||
|             }, |             }, | ||||||
|  |             PaymentMethodUpdate::ConnectorNetworkTransactionIdAndMandateDetailsUpdate { | ||||||
|  |                 connector_mandate_details, | ||||||
|  |                 network_transaction_id, | ||||||
|  |             } => Self { | ||||||
|  |                 connector_mandate_details: connector_mandate_details | ||||||
|  |                     .map(|mandate_details| mandate_details.expose()), | ||||||
|  |                 network_transaction_id: network_transaction_id.map(|txn_id| txn_id.expose()), | ||||||
|  |                 last_modified: common_utils::date_time::now(), | ||||||
|  |                 status: None, | ||||||
|  |                 metadata: None, | ||||||
|  |                 payment_method_data: None, | ||||||
|  |                 last_used_at: None, | ||||||
|  |                 locker_id: None, | ||||||
|  |                 payment_method: None, | ||||||
|  |                 updated_by: None, | ||||||
|  |                 payment_method_issuer: None, | ||||||
|  |                 payment_method_type: None, | ||||||
|  |                 network_token_requestor_reference_id: None, | ||||||
|  |                 network_token_locker_id: None, | ||||||
|  |                 network_token_payment_method_data: None, | ||||||
|  |             }, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,3 +7,15 @@ pub struct VerifyWebhookSource; | |||||||
| pub struct ConnectorMandateDetails { | pub struct ConnectorMandateDetails { | ||||||
|     pub connector_mandate_id: masking::Secret<String>, |     pub connector_mandate_id: masking::Secret<String>, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Clone, Serialize)] | ||||||
|  | pub struct ConnectorNetworkTxnId(masking::Secret<String>); | ||||||
|  |  | ||||||
|  | impl ConnectorNetworkTxnId { | ||||||
|  |     pub fn new(txn_id: masking::Secret<String>) -> Self { | ||||||
|  |         Self(txn_id) | ||||||
|  |     } | ||||||
|  |     pub fn get_id(&self) -> &masking::Secret<String> { | ||||||
|  |         &self.0 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -264,4 +264,15 @@ pub trait IncomingWebhook: ConnectorCommon + Sync { | |||||||
|     > { |     > { | ||||||
|         Ok(None) |         Ok(None) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// fn get_network_txn_id | ||||||
|  |     fn get_network_txn_id( | ||||||
|  |         &self, | ||||||
|  |         _request: &IncomingWebhookRequestDetails<'_>, | ||||||
|  |     ) -> CustomResult< | ||||||
|  |         Option<hyperswitch_domain_models::router_flow_types::ConnectorNetworkTxnId>, | ||||||
|  |         errors::ConnectorError, | ||||||
|  |     > { | ||||||
|  |         Ok(None) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1929,6 +1929,27 @@ impl api::IncomingWebhook for Adyen { | |||||||
|                 }); |                 }); | ||||||
|         Ok(mandate_reference) |         Ok(mandate_reference) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn get_network_txn_id( | ||||||
|  |         &self, | ||||||
|  |         request: &api::IncomingWebhookRequestDetails<'_>, | ||||||
|  |     ) -> CustomResult< | ||||||
|  |         Option<hyperswitch_domain_models::router_flow_types::ConnectorNetworkTxnId>, | ||||||
|  |         errors::ConnectorError, | ||||||
|  |     > { | ||||||
|  |         let notif = get_webhook_object_from_body(request.body) | ||||||
|  |             .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; | ||||||
|  |         let optional_network_txn_id = | ||||||
|  |             notif | ||||||
|  |                 .additional_data | ||||||
|  |                 .network_tx_reference | ||||||
|  |                 .map(|network_txn_id| { | ||||||
|  |                     hyperswitch_domain_models::router_flow_types::ConnectorNetworkTxnId::new( | ||||||
|  |                         network_txn_id, | ||||||
|  |                     ) | ||||||
|  |                 }); | ||||||
|  |         Ok(optional_network_txn_id) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl api::Dispute for Adyen {} | impl api::Dispute for Adyen {} | ||||||
|  | |||||||
| @ -4268,6 +4268,7 @@ pub struct AdyenAdditionalDataWH { | |||||||
|     /// Enable recurring details in dashboard to receive this ID, https://docs.adyen.com/online-payments/tokenization/create-and-use-tokens#test-and-go-live |     /// Enable recurring details in dashboard to receive this ID, https://docs.adyen.com/online-payments/tokenization/create-and-use-tokens#test-and-go-live | ||||||
|     #[serde(rename = "recurring.recurringDetailReference")] |     #[serde(rename = "recurring.recurringDetailReference")] | ||||||
|     pub recurring_detail_reference: Option<Secret<String>>, |     pub recurring_detail_reference: Option<Secret<String>>, | ||||||
|  |     pub network_tx_reference: Option<Secret<String>>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Deserialize)] | #[derive(Debug, Deserialize)] | ||||||
|  | |||||||
| @ -1817,11 +1817,20 @@ async fn update_connector_mandate_details( | |||||||
|         .switch() |         .switch() | ||||||
|         .attach_printable("Could not find connector mandate details in incoming webhook body")?; |         .attach_printable("Could not find connector mandate details in incoming webhook body")?; | ||||||
|  |  | ||||||
|     if let Some(webhook_mandate_details) = webhook_connector_mandate_details { |     let webhook_connector_network_transaction_id = connector | ||||||
|  |         .get_network_txn_id(request_details) | ||||||
|  |         .switch() | ||||||
|  |         .attach_printable( | ||||||
|  |             "Could not find connector network transaction id in incoming webhook body", | ||||||
|  |         )?; | ||||||
|  |  | ||||||
|  |     // Either one OR both of the fields are present | ||||||
|  |     if webhook_connector_mandate_details.is_some() | ||||||
|  |         || webhook_connector_network_transaction_id.is_some() | ||||||
|  |     { | ||||||
|         let payment_attempt = |         let payment_attempt = | ||||||
|             get_payment_attempt_from_object_reference_id(state, object_ref_id, merchant_account) |             get_payment_attempt_from_object_reference_id(state, object_ref_id, merchant_account) | ||||||
|                 .await?; |                 .await?; | ||||||
|  |  | ||||||
|         if let Some(ref payment_method_id) = payment_attempt.payment_method_id { |         if let Some(ref payment_method_id) = payment_attempt.payment_method_id { | ||||||
|             let key_manager_state = &state.into(); |             let key_manager_state = &state.into(); | ||||||
|             let payment_method_info = state |             let payment_method_info = state | ||||||
| @ -1835,6 +1844,9 @@ async fn update_connector_mandate_details( | |||||||
|                 .await |                 .await | ||||||
|                 .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; |                 .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; | ||||||
|  |  | ||||||
|  |             // Update connector's mandate details | ||||||
|  |             let updated_connector_mandate_details = | ||||||
|  |                 if let Some(webhook_mandate_details) = webhook_connector_mandate_details { | ||||||
|                     let mandate_details = payment_method_info |                     let mandate_details = payment_method_info | ||||||
|                         .connector_mandate_details |                         .connector_mandate_details | ||||||
|                         .clone() |                         .clone() | ||||||
| @ -1859,30 +1871,9 @@ async fn update_connector_mandate_details( | |||||||
|                         }) |                         }) | ||||||
|                         .unwrap_or(true) |                         .unwrap_or(true) | ||||||
|                     { |                     { | ||||||
|                 let updated_connector_mandate_details = insert_mandate_details( |  | ||||||
|                     &payment_attempt, |  | ||||||
|                     &webhook_mandate_details, |  | ||||||
|                     mandate_details, |  | ||||||
|                 )?; |  | ||||||
|                 let pm_update = diesel_models::PaymentMethodUpdate::ConnectorMandateDetailsUpdate { |  | ||||||
|                     connector_mandate_details: updated_connector_mandate_details, |  | ||||||
|                 }; |  | ||||||
|  |  | ||||||
|                 state |  | ||||||
|                     .store |  | ||||||
|                     .update_payment_method( |  | ||||||
|                         key_manager_state, |  | ||||||
|                         key_store, |  | ||||||
|                         payment_method_info, |  | ||||||
|                         pm_update, |  | ||||||
|                         merchant_account.storage_scheme, |  | ||||||
|                     ) |  | ||||||
|                     .await |  | ||||||
|                     .change_context(errors::ApiErrorResponse::InternalServerError) |  | ||||||
|                     .attach_printable("Failed to update payment method in db")?; |  | ||||||
|                         // Update the payment attempt to maintain consistency across tables. |                         // Update the payment attempt to maintain consistency across tables. | ||||||
|  |                         let (mandate_metadata, connector_mandate_request_reference_id) = | ||||||
|                 let (mandate_metadata, connector_mandate_request_reference_id) = payment_attempt |                             payment_attempt | ||||||
|                                 .connector_mandate_detail |                                 .connector_mandate_detail | ||||||
|                                 .as_ref() |                                 .as_ref() | ||||||
|                                 .map(|details| { |                                 .map(|details| { | ||||||
| @ -1905,7 +1896,8 @@ async fn update_connector_mandate_details( | |||||||
|                             connector_mandate_request_reference_id, |                             connector_mandate_request_reference_id, | ||||||
|                         }; |                         }; | ||||||
|  |  | ||||||
|                 let attempt_update = storage::PaymentAttemptUpdate::ConnectorMandateDetailUpdate { |                         let attempt_update = | ||||||
|  |                             storage::PaymentAttemptUpdate::ConnectorMandateDetailUpdate { | ||||||
|                                 connector_mandate_detail: Some(connector_mandate_reference_id), |                                 connector_mandate_detail: Some(connector_mandate_reference_id), | ||||||
|                                 updated_by: merchant_account.storage_scheme.to_string(), |                                 updated_by: merchant_account.storage_scheme.to_string(), | ||||||
|                             }; |                             }; | ||||||
| @ -1919,11 +1911,40 @@ async fn update_connector_mandate_details( | |||||||
|                             ) |                             ) | ||||||
|                             .await |                             .await | ||||||
|                             .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; |                             .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; | ||||||
|  |  | ||||||
|  |                         insert_mandate_details( | ||||||
|  |                             &payment_attempt, | ||||||
|  |                             &webhook_mandate_details, | ||||||
|  |                             mandate_details, | ||||||
|  |                         )? | ||||||
|                     } else { |                     } else { | ||||||
|                         logger::info!( |                         logger::info!( | ||||||
|                         "Skipping connector mandate details update since they are already present." |                         "Skipping connector mandate details update since they are already present." | ||||||
|                     ); |                     ); | ||||||
|  |                         None | ||||||
|                     } |                     } | ||||||
|  |                 } else { | ||||||
|  |                     None | ||||||
|  |                 }; | ||||||
|  |  | ||||||
|  |             let pm_update = diesel_models::PaymentMethodUpdate::ConnectorNetworkTransactionIdAndMandateDetailsUpdate { | ||||||
|  |                 connector_mandate_details: updated_connector_mandate_details.map(masking::Secret::new), | ||||||
|  |                 network_transaction_id: webhook_connector_network_transaction_id | ||||||
|  |                     .map(|webhook_network_transaction_id| webhook_network_transaction_id.get_id().clone()), | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             state | ||||||
|  |                 .store | ||||||
|  |                 .update_payment_method( | ||||||
|  |                     key_manager_state, | ||||||
|  |                     key_store, | ||||||
|  |                     payment_method_info, | ||||||
|  |                     pm_update, | ||||||
|  |                     merchant_account.storage_scheme, | ||||||
|  |                 ) | ||||||
|  |                 .await | ||||||
|  |                 .change_context(errors::ApiErrorResponse::InternalServerError) | ||||||
|  |                 .attach_printable("Failed to update payment method in db")?; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     Ok(()) |     Ok(()) | ||||||
|  | |||||||
| @ -321,6 +321,19 @@ impl api::IncomingWebhook for ConnectorEnum { | |||||||
|             Self::New(connector) => connector.get_mandate_details(request), |             Self::New(connector) => connector.get_mandate_details(request), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn get_network_txn_id( | ||||||
|  |         &self, | ||||||
|  |         request: &IncomingWebhookRequestDetails<'_>, | ||||||
|  |     ) -> CustomResult< | ||||||
|  |         Option<hyperswitch_domain_models::router_flow_types::ConnectorNetworkTxnId>, | ||||||
|  |         errors::ConnectorError, | ||||||
|  |     > { | ||||||
|  |         match self { | ||||||
|  |             Self::Old(connector) => connector.get_network_txn_id(request), | ||||||
|  |             Self::New(connector) => connector.get_network_txn_id(request), | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl api::ConnectorTransactionId for ConnectorEnum { | impl api::ConnectorTransactionId for ConnectorEnum { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Kashif
					Kashif