mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 01:27:31 +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"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
use masking::Secret;
|
||||
use masking::{ExposeInterface, Secret};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::PrimitiveDateTime;
|
||||
|
||||
@ -253,6 +253,10 @@ pub enum PaymentMethodUpdate {
|
||||
network_token_locker_id: Option<String>,
|
||||
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"))]
|
||||
@ -646,6 +650,27 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
|
||||
network_token_locker_id,
|
||||
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 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)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
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 {}
|
||||
|
||||
@ -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
|
||||
#[serde(rename = "recurring.recurringDetailReference")]
|
||||
pub recurring_detail_reference: Option<Secret<String>>,
|
||||
pub network_tx_reference: Option<Secret<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
||||
@ -1817,11 +1817,20 @@ async fn update_connector_mandate_details(
|
||||
.switch()
|
||||
.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 =
|
||||
get_payment_attempt_from_object_reference_id(state, object_ref_id, merchant_account)
|
||||
.await?;
|
||||
|
||||
if let Some(ref payment_method_id) = payment_attempt.payment_method_id {
|
||||
let key_manager_state = &state.into();
|
||||
let payment_method_info = state
|
||||
@ -1835,6 +1844,9 @@ async fn update_connector_mandate_details(
|
||||
.await
|
||||
.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
|
||||
.connector_mandate_details
|
||||
.clone()
|
||||
@ -1859,30 +1871,9 @@ async fn update_connector_mandate_details(
|
||||
})
|
||||
.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.
|
||||
|
||||
let (mandate_metadata, connector_mandate_request_reference_id) = payment_attempt
|
||||
let (mandate_metadata, connector_mandate_request_reference_id) =
|
||||
payment_attempt
|
||||
.connector_mandate_detail
|
||||
.as_ref()
|
||||
.map(|details| {
|
||||
@ -1905,7 +1896,8 @@ async fn update_connector_mandate_details(
|
||||
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),
|
||||
updated_by: merchant_account.storage_scheme.to_string(),
|
||||
};
|
||||
@ -1919,11 +1911,40 @@ async fn update_connector_mandate_details(
|
||||
)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
|
||||
|
||||
insert_mandate_details(
|
||||
&payment_attempt,
|
||||
&webhook_mandate_details,
|
||||
mandate_details,
|
||||
)?
|
||||
} else {
|
||||
logger::info!(
|
||||
"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(())
|
||||
|
||||
@ -321,6 +321,19 @@ impl api::IncomingWebhook for ConnectorEnum {
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user