diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 13c1da248ac3..59f0cd63cd92 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -2293,11 +2293,16 @@ pub struct PaymentListResponse { #[derive(Setter, Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)] pub struct AuthorizationResponse { + /// The unique identifier of authorization pub authorization_id: String, + /// Amount the authorization has been made for pub amount: i64, #[schema(value_type= AuthorizationStatus)] + /// The status of the authorization pub status: common_enums::AuthorizationStatus, + /// The code sent by the connector for authorization pub code: Option, + /// The message sent by the connector for authorization pub message: Option, } diff --git a/crates/data_models/src/payments/payment_attempt.rs b/crates/data_models/src/payments/payment_attempt.rs index d122374d4606..f7b849f1d4e1 100644 --- a/crates/data_models/src/payments/payment_attempt.rs +++ b/crates/data_models/src/payments/payment_attempt.rs @@ -359,8 +359,9 @@ pub enum PaymentAttemptUpdate { connector: Option, updated_by: String, }, - AmountUpdate { + IncrementalAuthorizationAmountUpdate { amount: i64, + amount_capturable: i64, }, } diff --git a/crates/data_models/src/payments/payment_intent.rs b/crates/data_models/src/payments/payment_intent.rs index 8e865354cf9f..d7edcfdf1791 100644 --- a/crates/data_models/src/payments/payment_intent.rs +++ b/crates/data_models/src/payments/payment_intent.rs @@ -187,7 +187,7 @@ pub enum PaymentIntentUpdate { surcharge_applicable: bool, updated_by: String, }, - AmountUpdate { + IncrementalAuthorizationAmountUpdate { amount: i64, }, AuthorizationCountUpdate { @@ -389,7 +389,7 @@ impl From for PaymentIntentUpdateInternal { updated_by, ..Default::default() }, - PaymentIntentUpdate::AmountUpdate { amount } => Self { + PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } => Self { amount: Some(amount), ..Default::default() }, diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index fa091e9d8daf..b1e8e144a9e3 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -269,8 +269,9 @@ pub enum PaymentAttemptUpdate { connector: Option, updated_by: String, }, - AmountUpdate { + IncrementalAuthorizationAmountUpdate { amount: i64, + amount_capturable: i64, }, } @@ -682,8 +683,12 @@ impl From for PaymentAttemptUpdateInternal { updated_by, ..Default::default() }, - PaymentAttemptUpdate::AmountUpdate { amount } => Self { + PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { + amount, + amount_capturable, + } => Self { amount: Some(amount), + amount_capturable: Some(amount_capturable), ..Default::default() }, } diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index ea0370404e61..37d25cda3e51 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -190,7 +190,7 @@ pub enum PaymentIntentUpdate { surcharge_applicable: Option, updated_by: String, }, - AmountUpdate { + IncrementalAuthorizationAmountUpdate { amount: i64, }, AuthorizationCountUpdate { @@ -460,7 +460,7 @@ impl From for PaymentIntentUpdateInternal { updated_by, ..Default::default() }, - PaymentIntentUpdate::AmountUpdate { amount } => Self { + PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } => Self { amount: Some(amount), ..Default::default() }, diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 6a0aa6a1ac6f..f5fcb6380713 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -84,8 +84,8 @@ impl PostUpdateTracker, types::PaymentsIncrementalAu async fn update_tracker<'b>( &'b self, db: &'b AppState, - payment_id: &api::PaymentIdType, - payment_data: PaymentData, + _payment_id: &api::PaymentIdType, + mut payment_data: PaymentData, router_data: types::RouterData< F, types::PaymentsIncrementalAuthorizationData, @@ -96,14 +96,116 @@ impl PostUpdateTracker, types::PaymentsIncrementalAu where F: 'b + Send, { - Box::pin(payment_response_update_tracker( - db, - payment_id, - payment_data, - router_data, - storage_scheme, - )) - .await + let incremental_authorization_details = payment_data + .incremental_authorization_details + .clone() + .ok_or_else(|| { + report!(errors::ApiErrorResponse::InternalServerError) + .attach_printable("missing incremental_authorization_details in payment_data") + })?; + // Update payment_intent and payment_attempt 'amount' if incremental_authorization is successful + let (option_payment_attempt_update, option_payment_intent_update) = + match router_data.response.clone() { + Err(_) => (None, None), + Ok(types::PaymentsResponseData::IncrementalAuthorizationResponse { + status, + .. + }) => { + if status == AuthorizationStatus::Success { + (Some( + storage::PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { + amount: incremental_authorization_details.total_amount, + amount_capturable: incremental_authorization_details.total_amount, + }, + ), Some( + storage::PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { + amount: incremental_authorization_details.total_amount, + }, + )) + } else { + (None, None) + } + } + _ => Err(errors::ApiErrorResponse::InternalServerError) + .into_report() + .attach_printable("unexpected response in incremental_authorization flow")?, + }; + //payment_attempt update + if let Some(payment_attempt_update) = option_payment_attempt_update { + payment_data.payment_attempt = db + .store + .update_payment_attempt_with_attempt_id( + payment_data.payment_attempt.clone(), + payment_attempt_update, + storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + } + // payment_intent update + if let Some(payment_intent_update) = option_payment_intent_update { + payment_data.payment_intent = db + .store + .update_payment_intent( + payment_data.payment_intent.clone(), + payment_intent_update, + storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + } + // Update the status of authorization record + let authorization_update = match &router_data.response { + Err(res) => Ok(storage::AuthorizationUpdate::StatusUpdate { + status: AuthorizationStatus::Failure, + code: Some(res.code.clone()), + message: Some(res.message.clone()), + connector_authorization_id: None, + }), + Ok(types::PaymentsResponseData::IncrementalAuthorizationResponse { + status, + code, + message, + connector_authorization_id, + }) => Ok(storage::AuthorizationUpdate::StatusUpdate { + status: status.clone(), + code: code.clone(), + message: message.clone(), + connector_authorization_id: connector_authorization_id.clone(), + }), + Ok(_) => Err(errors::ApiErrorResponse::InternalServerError) + .into_report() + .attach_printable("unexpected response in incremental_authorization flow"), + }?; + let authorization_id = incremental_authorization_details + .authorization_id + .clone() + .ok_or( + report!(errors::ApiErrorResponse::InternalServerError).attach_printable( + "missing authorization_id in incremental_authorization_details in payment_data", + ), + )?; + db.store + .update_authorization_by_merchant_id_authorization_id( + router_data.merchant_id.clone(), + authorization_id, + authorization_update, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed while updating authorization")?; + //Fetch all the authorizations of the payment and send in incremental authorization response + let authorizations = db + .store + .find_all_authorizations_by_merchant_id_payment_id( + &router_data.merchant_id, + &payment_data.payment_intent.payment_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed while retrieving authorizations")?; + payment_data.authorizations = authorizations; + Ok(payment_data) } } @@ -344,39 +446,35 @@ async fn payment_response_update_tracker( ) -> RouterResult> { let (capture_update, mut payment_attempt_update) = match router_data.response.clone() { Err(err) => { - // If incremental authorization don't update payment_attempt in error_response - if payment_data.incremental_authorization_details.is_some() { - (None, None) - } else { - let (capture_update, attempt_update) = match payment_data.multiple_capture_data { - Some(multiple_capture_data) => { - let capture_update = storage::CaptureUpdate::ErrorUpdate { - status: match err.status_code { - 500..=511 => storage::enums::CaptureStatus::Pending, - _ => storage::enums::CaptureStatus::Failed, - }, - error_code: Some(err.code), - error_message: Some(err.message), - error_reason: err.reason, - }; - let capture_update_list = vec![( - multiple_capture_data.get_latest_capture().clone(), - capture_update, - )]; - (Some((multiple_capture_data, capture_update_list)), None) - } - None => { - let connector_name = router_data.connector.to_string(); - let flow_name = core_utils::get_flow_name::()?; - let option_gsm = payments_helpers::get_gsm_record( - state, - Some(err.code.clone()), - Some(err.message.clone()), - connector_name, - flow_name.clone(), - ) - .await; - let status = + let (capture_update, attempt_update) = match payment_data.multiple_capture_data { + Some(multiple_capture_data) => { + let capture_update = storage::CaptureUpdate::ErrorUpdate { + status: match err.status_code { + 500..=511 => storage::enums::CaptureStatus::Pending, + _ => storage::enums::CaptureStatus::Failed, + }, + error_code: Some(err.code), + error_message: Some(err.message), + error_reason: err.reason, + }; + let capture_update_list = vec![( + multiple_capture_data.get_latest_capture().clone(), + capture_update, + )]; + (Some((multiple_capture_data, capture_update_list)), None) + } + None => { + let connector_name = router_data.connector.to_string(); + let flow_name = core_utils::get_flow_name::()?; + let option_gsm = payments_helpers::get_gsm_record( + state, + Some(err.code.clone()), + Some(err.message.clone()), + connector_name, + flow_name.clone(), + ) + .await; + let status = // mark previous attempt status for technical failures in PSync flow if flow_name == "PSync" { match err.status_code { @@ -390,33 +488,32 @@ async fn payment_response_update_tracker( _ => storage::enums::AttemptStatus::Failure, } }; - ( - None, - Some(storage::PaymentAttemptUpdate::ErrorUpdate { - connector: None, - status, - error_message: Some(Some(err.message)), - error_code: Some(Some(err.code)), - error_reason: Some(err.reason), - amount_capturable: if status.is_terminal_status() - || router_data - .status - .maps_to_intent_status(enums::IntentStatus::Processing) - { - Some(0) - } else { - None - }, - updated_by: storage_scheme.to_string(), - unified_code: option_gsm.clone().map(|gsm| gsm.unified_code), - unified_message: option_gsm.map(|gsm| gsm.unified_message), - connector_transaction_id: err.connector_transaction_id, - }), - ) - } - }; - (capture_update, attempt_update) - } + ( + None, + Some(storage::PaymentAttemptUpdate::ErrorUpdate { + connector: None, + status, + error_message: Some(Some(err.message)), + error_code: Some(Some(err.code)), + error_reason: Some(err.reason), + amount_capturable: if status.is_terminal_status() + || router_data + .status + .maps_to_intent_status(enums::IntentStatus::Processing) + { + Some(0) + } else { + None + }, + updated_by: storage_scheme.to_string(), + unified_code: option_gsm.clone().map(|gsm| gsm.unified_code), + unified_message: option_gsm.map(|gsm| gsm.unified_message), + connector_transaction_id: err.connector_transaction_id, + }), + ) + } + }; + (capture_update, attempt_update) } Ok(payments_response) => match payments_response { types::PaymentsResponseData::PreProcessingResponse { @@ -580,6 +677,7 @@ async fn payment_response_update_tracker( types::PaymentsResponseData::TokenizationResponse { .. } => (None, None), types::PaymentsResponseData::ConnectorCustomerResponse { .. } => (None, None), types::PaymentsResponseData::ThreeDSEnrollmentResponse { .. } => (None, None), + types::PaymentsResponseData::IncrementalAuthorizationResponse { .. } => (None, None), types::PaymentsResponseData::MultipleCaptureResponse { capture_sync_response_list, } => match payment_data.multiple_capture_data { @@ -592,27 +690,6 @@ async fn payment_response_update_tracker( } None => (None, None), }, - types::PaymentsResponseData::IncrementalAuthorizationResponse { status, .. } => { - if status == AuthorizationStatus::Success { - ( - None, - Some(storage::PaymentAttemptUpdate::AmountUpdate { - amount: payment_data - .incremental_authorization_details - .clone() - .map(|details| details.total_amount) - .ok_or_else(|| { - report!(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "missing incremental_authorization_details in payment_data", - ) - })?, - }), - ) - } else { - (None, None) - } - } }, }; payment_data.multiple_capture_data = match capture_update { @@ -704,33 +781,6 @@ async fn payment_response_update_tracker( None }, }, - Ok(types::PaymentsResponseData::IncrementalAuthorizationResponse { status, .. }) => { - if status == &AuthorizationStatus::Success { - storage::PaymentIntentUpdate::AmountUpdate { - amount: payment_data - .incremental_authorization_details - .clone() - .map(|details| details.total_amount) - .ok_or_else(|| { - report!(errors::ApiErrorResponse::InternalServerError).attach_printable( - "missing incremental_authorization_details in payment_data", - ) - })?, - } - } else { - storage::PaymentIntentUpdate::ResponseUpdate { - status: api_models::enums::IntentStatus::foreign_from( - payment_data.payment_attempt.status, - ), - return_url: router_data.return_url.clone(), - amount_captured, - updated_by: storage_scheme.to_string(), - incremental_authorization_allowed: payment_data - .payment_intent - .incremental_authorization_allowed, - } - } - } Ok(_) => storage::PaymentIntentUpdate::ResponseUpdate { status: api_models::enums::IntentStatus::foreign_from( payment_data.payment_attempt.status, @@ -744,58 +794,6 @@ async fn payment_response_update_tracker( }, }; - //Update the authorization record if it's an incremental authorization flow - if let Some(details) = &payment_data.incremental_authorization_details { - let authorization_update = match &router_data.response { - Err(res) => Ok(storage::AuthorizationUpdate::StatusUpdate { - status: AuthorizationStatus::Failure, - code: Some(res.code.clone()), - message: Some(res.message.clone()), - connector_authorization_id: None, - }), - Ok(types::PaymentsResponseData::IncrementalAuthorizationResponse { - status, - code, - message, - connector_authorization_id, - }) => Ok(storage::AuthorizationUpdate::StatusUpdate { - status: status.clone(), - code: code.clone(), - message: message.clone(), - connector_authorization_id: connector_authorization_id.clone(), - }), - Ok(_) => Err(errors::ApiErrorResponse::InternalServerError) - .into_report() - .attach_printable("unexpected response in incremental_authorization flow"), - }?; - let authorization_id = details.authorization_id.clone().ok_or( - report!(errors::ApiErrorResponse::InternalServerError).attach_printable( - "missing authorization_id in incremental_authorization_details in payment_data", - ), - )?; - state - .store - .update_authorization_by_merchant_id_authorization_id( - router_data.merchant_id.clone(), - authorization_id, - authorization_update, - ) - .await - .to_not_found_response(errors::ApiErrorResponse::InternalServerError) - .attach_printable("failed while updating authorization")?; - //Fetch all the authorizations of the payment and send in incremental authorization response - let authorizations = state - .store - .find_all_authorizations_by_merchant_id_payment_id( - &router_data.merchant_id, - &payment_data.payment_intent.payment_id, - ) - .await - .to_not_found_response(errors::ApiErrorResponse::InternalServerError) - .attach_printable("failed while retrieving authorizations")?; - payment_data.authorizations = authorizations; - } - let m_db = state.clone().store; let m_payment_data_payment_intent = payment_data.payment_intent.clone(); let m_payment_intent_update = payment_intent_update.clone(); diff --git a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs index c581fc8090cc..db10349bc272 100644 --- a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs +++ b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs @@ -238,10 +238,16 @@ impl .store .insert_authorization(authorization_new.clone()) .await - .to_not_found_response(errors::ApiErrorResponse::InternalServerError) + .to_duplicate_response(errors::ApiErrorResponse::GenericDuplicateError { + message: format!( + "Authorization with authorization_id {} already exists", + authorization_new.authorization_id + ), + }) .attach_printable("failed while inserting new authorization")?; // Update authorization_count in payment_intent - db.store + payment_data.payment_intent = db + .store .update_payment_intent( payment_data.payment_intent.clone(), storage::PaymentIntentUpdate::AuthorizationCountUpdate { diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 905fecdaa363..9f351979f289 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -1467,7 +1467,13 @@ impl DataModelExt for PaymentAttemptUpdate { connector, updated_by, }, - Self::AmountUpdate { amount } => DieselPaymentAttemptUpdate::AmountUpdate { amount }, + Self::IncrementalAuthorizationAmountUpdate { + amount, + amount_capturable, + } => DieselPaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { + amount, + amount_capturable, + }, } } @@ -1729,7 +1735,13 @@ impl DataModelExt for PaymentAttemptUpdate { connector, updated_by, }, - DieselPaymentAttemptUpdate::AmountUpdate { amount } => Self::AmountUpdate { amount }, + DieselPaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { + amount, + amount_capturable, + } => Self::IncrementalAuthorizationAmountUpdate { + amount, + amount_capturable, + }, } } } diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index d29636e46331..90bb21190c39 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -1043,7 +1043,9 @@ impl DataModelExt for PaymentIntentUpdate { surcharge_applicable: Some(surcharge_applicable), updated_by, }, - Self::AmountUpdate { amount } => DieselPaymentIntentUpdate::AmountUpdate { amount }, + Self::IncrementalAuthorizationAmountUpdate { amount } => { + DieselPaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } + } Self::AuthorizationCountUpdate { authorization_count, } => DieselPaymentIntentUpdate::AuthorizationCountUpdate { diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 178386cd4780..0e003c1d4764 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -2897,21 +2897,25 @@ ], "properties": { "authorization_id": { - "type": "string" + "type": "string", + "description": "The unique identifier of authorization" }, "amount": { "type": "integer", - "format": "int64" + "format": "int64", + "description": "Amount the authorization has been made for" }, "status": { "$ref": "#/components/schemas/AuthorizationStatus" }, "code": { "type": "string", + "description": "The code sent by the connector for authorization", "nullable": true }, "message": { "type": "string", + "description": "The message sent by the connector for authorization", "nullable": true } }