feat(router): store network_reference_id against the payment_method_id in the payment_method_table (#4041)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Shankar Singh C
2024-04-04 13:55:00 +05:30
committed by GitHub
parent 9ebe0f4371
commit 21e2d78117
7 changed files with 110 additions and 20 deletions

View File

@ -126,6 +126,10 @@ pub enum PaymentMethodUpdate {
LastUsedUpdate { LastUsedUpdate {
last_used_at: PrimitiveDateTime, last_used_at: PrimitiveDateTime,
}, },
NetworkTransactionIdAndStatusUpdate {
network_transaction_id: Option<String>,
status: Option<storage_enums::PaymentMethodStatus>,
},
StatusUpdate { StatusUpdate {
status: Option<storage_enums::PaymentMethodStatus>, status: Option<storage_enums::PaymentMethodStatus>,
}, },
@ -140,6 +144,7 @@ pub struct PaymentMethodUpdateInternal {
metadata: Option<serde_json::Value>, metadata: Option<serde_json::Value>,
payment_method_data: Option<Encryption>, payment_method_data: Option<Encryption>,
last_used_at: Option<PrimitiveDateTime>, last_used_at: Option<PrimitiveDateTime>,
network_transaction_id: Option<String>,
status: Option<storage_enums::PaymentMethodStatus>, status: Option<storage_enums::PaymentMethodStatus>,
connector_mandate_details: Option<serde_json::Value>, connector_mandate_details: Option<serde_json::Value>,
} }
@ -159,6 +164,7 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
metadata, metadata,
payment_method_data: None, payment_method_data: None,
last_used_at: None, last_used_at: None,
network_transaction_id: None,
status: None, status: None,
connector_mandate_details: None, connector_mandate_details: None,
}, },
@ -168,6 +174,7 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
metadata: None, metadata: None,
payment_method_data, payment_method_data,
last_used_at: None, last_used_at: None,
network_transaction_id: None,
status: None, status: None,
connector_mandate_details: None, connector_mandate_details: None,
}, },
@ -175,13 +182,26 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
metadata: None, metadata: None,
payment_method_data: None, payment_method_data: None,
last_used_at: Some(last_used_at), last_used_at: Some(last_used_at),
network_transaction_id: None,
status: None, status: None,
connector_mandate_details: None, connector_mandate_details: None,
}, },
PaymentMethodUpdate::NetworkTransactionIdAndStatusUpdate {
network_transaction_id,
status,
} => Self {
metadata: None,
payment_method_data: None,
last_used_at: None,
network_transaction_id,
status,
connector_mandate_details: None,
},
PaymentMethodUpdate::StatusUpdate { status } => Self { PaymentMethodUpdate::StatusUpdate { status } => Self {
metadata: None, metadata: None,
payment_method_data: None, payment_method_data: None,
last_used_at: None, last_used_at: None,
network_transaction_id: None,
status, status,
connector_mandate_details: None, connector_mandate_details: None,
}, },
@ -193,6 +213,7 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
last_used_at: None, last_used_at: None,
status: None, status: None,
connector_mandate_details, connector_mandate_details,
network_transaction_id: None,
}, },
} }
} }

View File

@ -2231,6 +2231,7 @@ impl Deref for PaymentIntentSyncResponse {
pub struct StripeAdditionalCardDetails { pub struct StripeAdditionalCardDetails {
checks: Option<Value>, checks: Option<Value>,
three_d_secure: Option<Value>, three_d_secure: Option<Value>,
network_transaction_id: Option<String>,
} }
#[derive(Deserialize, Clone, Debug, PartialEq, Eq, Serialize)] #[derive(Deserialize, Clone, Debug, PartialEq, Eq, Serialize)]
@ -2685,12 +2686,24 @@ impl<F, T>
item.response.id.clone(), item.response.id.clone(),
)) ))
} else { } else {
let network_transaction_id = match item.response.latest_attempt {
Some(LatestAttempt::PaymentIntentAttempt(attempt)) => attempt
.payment_method_details
.and_then(|payment_method_details| match payment_method_details {
StripePaymentMethodDetailsResponse::Card { card } => {
card.network_transaction_id
}
_ => None,
}),
_ => None,
};
Ok(types::PaymentsResponseData::TransactionResponse { Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()),
redirection_data, redirection_data,
mandate_reference, mandate_reference,
connector_metadata: None, connector_metadata: None,
network_txn_id: Option::foreign_from(item.response.latest_attempt), network_txn_id: network_transaction_id,
connector_response_reference_id: Some(item.response.id), connector_response_reference_id: Some(item.response.id),
incremental_authorization_allowed: None, incremental_authorization_allowed: None,
}) })
@ -3140,6 +3153,7 @@ pub struct LatestPaymentAttempt {
pub payment_method_options: Option<StripePaymentMethodOptions>, pub payment_method_options: Option<StripePaymentMethodOptions>,
pub payment_method_details: Option<StripePaymentMethodDetailsResponse>, pub payment_method_details: Option<StripePaymentMethodDetailsResponse>,
} }
// #[derive(Deserialize, Debug, Clone, Eq, PartialEq)] // #[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
// pub struct Card // pub struct Card
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Default, Eq, PartialEq)] #[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Default, Eq, PartialEq)]

View File

@ -87,6 +87,7 @@ pub async fn create_payment_method(
payment_method_data: Option<Encryption>, payment_method_data: Option<Encryption>,
key_store: &domain::MerchantKeyStore, key_store: &domain::MerchantKeyStore,
connector_mandate_details: Option<serde_json::Value>, connector_mandate_details: Option<serde_json::Value>,
network_transaction_id: Option<String>,
) -> errors::CustomResult<storage::PaymentMethod, errors::ApiErrorResponse> { ) -> errors::CustomResult<storage::PaymentMethod, errors::ApiErrorResponse> {
let customer = db let customer = db
.find_customer_by_customer_id_merchant_id(customer_id, merchant_id, key_store) .find_customer_by_customer_id_merchant_id(customer_id, merchant_id, key_store)
@ -107,6 +108,7 @@ pub async fn create_payment_method(
payment_method_data, payment_method_data,
connector_mandate_details, connector_mandate_details,
customer_acceptance: customer_acceptance.map(masking::Secret::new), customer_acceptance: customer_acceptance.map(masking::Secret::new),
network_transaction_id: network_transaction_id.to_owned(),
..storage::PaymentMethodNew::default() ..storage::PaymentMethodNew::default()
}) })
.await .await
@ -205,6 +207,7 @@ pub async fn get_or_insert_payment_method(
None, None,
locker_id, locker_id,
None, None,
None,
) )
.await .await
} else { } else {
@ -392,6 +395,7 @@ pub async fn add_payment_method(
None, None,
locker_id, locker_id,
None, None,
None,
) )
.await?; .await?;
} }
@ -412,6 +416,7 @@ pub async fn insert_payment_method(
customer_acceptance: Option<serde_json::Value>, customer_acceptance: Option<serde_json::Value>,
locker_id: Option<String>, locker_id: Option<String>,
connector_mandate_details: Option<serde_json::Value>, connector_mandate_details: Option<serde_json::Value>,
network_transaction_id: Option<String>,
) -> errors::RouterResult<diesel_models::PaymentMethod> { ) -> errors::RouterResult<diesel_models::PaymentMethod> {
let pm_card_details = resp let pm_card_details = resp
.card .card
@ -430,6 +435,7 @@ pub async fn insert_payment_method(
pm_data_encrypted, pm_data_encrypted,
key_store, key_store,
connector_mandate_details, connector_mandate_details,
network_transaction_id,
) )
.await .await
} }

View File

@ -7,7 +7,7 @@ use data_models::payments::payment_attempt::PaymentAttempt;
use error_stack::{report, ResultExt}; use error_stack::{report, ResultExt};
use futures::FutureExt; use futures::FutureExt;
use router_derive; use router_derive;
use router_env::{instrument, tracing}; use router_env::{instrument, logger, tracing};
use storage_impl::DataModelExt; use storage_impl::DataModelExt;
use tracing_futures::Instrument; use tracing_futures::Instrument;
@ -34,7 +34,7 @@ use crate::{
self, api, self, api,
storage::{self, enums}, storage::{self, enums},
transformers::{ForeignFrom, ForeignTryFrom}, transformers::{ForeignFrom, ForeignTryFrom},
CaptureSyncResponse, CaptureSyncResponse, ErrorResponse,
}, },
utils, utils,
}; };
@ -833,7 +833,7 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
}; };
let amount_captured = get_total_amount_captured( let amount_captured = get_total_amount_captured(
router_data.request, &router_data.request,
router_data.amount_captured, router_data.amount_captured,
router_data.status, router_data.status,
&payment_data, &payment_data,
@ -862,7 +862,13 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
}, },
}; };
update_payment_method_status(state, &mut payment_data, router_data.status).await?; update_payment_method_status_and_ntid(
state,
&mut payment_data,
router_data.status,
router_data.response.clone(),
)
.await?;
let m_db = state.clone().store; let m_db = state.clone().store;
let m_payment_data_payment_intent = payment_data.payment_intent.clone(); let m_payment_data_payment_intent = payment_data.payment_intent.clone();
let m_payment_intent_update = payment_intent_update.clone(); let m_payment_intent_update = payment_intent_update.clone();
@ -954,10 +960,11 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
Ok(payment_data) Ok(payment_data)
} }
async fn update_payment_method_status<F: Clone>( async fn update_payment_method_status_and_ntid<F: Clone>(
state: &AppState, state: &AppState,
payment_data: &mut PaymentData<F>, payment_data: &mut PaymentData<F>,
attempt_status: common_enums::AttemptStatus, attempt_status: common_enums::AttemptStatus,
payment_response: Result<types::PaymentsResponseData, ErrorResponse>,
) -> RouterResult<()> { ) -> RouterResult<()> {
if let Some(id) = &payment_data.payment_attempt.payment_method_id { if let Some(id) = &payment_data.payment_attempt.payment_method_id {
let pm = state let pm = state
@ -965,8 +972,20 @@ async fn update_payment_method_status<F: Clone>(
.find_payment_method(id) .find_payment_method(id)
.await .await
.to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?;
let network_transaction_id = payment_response
.map(|resp| match resp {
crate::types::PaymentsResponseData::TransactionResponse { network_txn_id, .. } => {
network_txn_id.to_owned()
}
_ => None,
})
.map_err(|err| {
logger::error!(error=?err, "Failed to obtain the network_transaction_id from payment response");
})
.ok()
.flatten();
if pm.status != common_enums::PaymentMethodStatus::Active let pm_update = if pm.status != common_enums::PaymentMethodStatus::Active
&& pm.status != attempt_status.into() && pm.status != attempt_status.into()
{ {
let updated_pm_status = common_enums::PaymentMethodStatus::from(attempt_status); let updated_pm_status = common_enums::PaymentMethodStatus::from(attempt_status);
@ -975,16 +994,23 @@ async fn update_payment_method_status<F: Clone>(
.payment_method_info .payment_method_info
.as_mut() .as_mut()
.map(|info| info.status = updated_pm_status); .map(|info| info.status = updated_pm_status);
let pm_update = storage::PaymentMethodUpdate::StatusUpdate { storage::PaymentMethodUpdate::NetworkTransactionIdAndStatusUpdate {
network_transaction_id,
status: Some(updated_pm_status), status: Some(updated_pm_status),
}; }
state } else {
.store storage::PaymentMethodUpdate::NetworkTransactionIdAndStatusUpdate {
.update_payment_method(pm, pm_update) network_transaction_id,
.await status: None,
.change_context(errors::ApiErrorResponse::InternalServerError) }
.attach_printable("Failed to update payment method in db")?; };
}
state
.store
.update_payment_method(pm, pm_update)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to update payment method in db")?;
}; };
Ok(()) Ok(())
} }
@ -1043,7 +1069,7 @@ fn get_capture_update_for_unmapped_capture_responses(
} }
fn get_total_amount_captured<F: Clone, T: types::Capturable>( fn get_total_amount_captured<F: Clone, T: types::Capturable>(
request: T, request: &T,
amount_captured: Option<i64>, amount_captured: Option<i64>,
router_data_status: enums::AttemptStatus, router_data_status: enums::AttemptStatus,
payment_data: &PaymentData<F>, payment_data: &PaymentData<F>,

View File

@ -399,6 +399,18 @@ async fn get_tracker_for_sync<
.to_not_found_response(errors::ApiErrorResponse::BusinessProfileNotFound { .to_not_found_response(errors::ApiErrorResponse::BusinessProfileNotFound {
id: profile_id.to_string(), id: profile_id.to_string(),
})?; })?;
let payment_method_info =
if let Some(ref payment_method_id) = payment_attempt.payment_method_id.clone() {
Some(
db.find_payment_method(payment_method_id)
.await
.to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)
.attach_printable("error retrieving payment method from DB")?,
)
} else {
None
};
let merchant_id = payment_intent.merchant_id.clone(); let merchant_id = payment_intent.merchant_id.clone();
let authentication = payment_attempt.authentication_id.clone().async_map(|authentication_id| async move { let authentication = payment_attempt.authentication_id.clone().async_map(|authentication_id| async move {
db.find_authentication_by_merchant_id_authentication_id( db.find_authentication_by_merchant_id_authentication_id(
@ -436,7 +448,7 @@ async fn get_tracker_for_sync<
token_data: None, token_data: None,
confirm: Some(request.force_sync), confirm: Some(request.force_sync),
payment_method_data: None, payment_method_data: None,
payment_method_info: None, payment_method_info,
force_sync: Some( force_sync: Some(
request.force_sync request.force_sync
&& (helpers::check_force_psync_precondition(&payment_attempt.status) && (helpers::check_force_psync_precondition(&payment_attempt.status)

View File

@ -57,6 +57,13 @@ where
.map(|token_filter| token_filter.long_lived_token) .map(|token_filter| token_filter.long_lived_token)
.unwrap_or(false); .unwrap_or(false);
let network_transaction_id = match responses.clone() {
types::PaymentsResponseData::TransactionResponse { network_txn_id, .. } => {
network_txn_id
}
_ => None,
};
let connector_token = if token_store { let connector_token = if token_store {
let tokens = resp let tokens = resp
.payment_method_token .payment_method_token
@ -255,6 +262,7 @@ where
pm_data_encrypted, pm_data_encrypted,
key_store, key_store,
connector_mandate_details, connector_mandate_details,
network_transaction_id,
) )
.await .await
} else { } else {
@ -337,6 +345,7 @@ where
customer_acceptance, customer_acceptance,
locker_id, locker_id,
connector_mandate_details, connector_mandate_details,
network_transaction_id,
) )
.await .await
} else { } else {
@ -459,6 +468,7 @@ where
pm_data_encrypted, pm_data_encrypted,
key_store, key_store,
connector_mandate_details, connector_mandate_details,
network_transaction_id,
) )
.await?; .await?;
} }

View File

@ -13,8 +13,8 @@ use crate::{
core::{ core::{
errors::{self, RouterResult}, errors::{self, RouterResult},
payment_methods::{ payment_methods::{
cards, transformers, cards,
transformers::{StoreCardReq, StoreGenericReq, StoreLockerReq}, transformers::{self, StoreCardReq, StoreGenericReq, StoreLockerReq},
vault, vault,
}, },
payments::{ payments::{
@ -389,6 +389,7 @@ pub async fn save_payout_data_to_locker(
card_details_encrypted, card_details_encrypted,
key_store, key_store,
None, None,
None,
) )
.await?; .await?;