Skip to content

Commit

Permalink
feat(router): add payments manual-update api (#5045)
Browse files Browse the repository at this point in the history
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
  • Loading branch information
sai-harsha-vardhan and hyperswitch-bot[bot] authored Jun 26, 2024
1 parent 2e1167a commit ed021c1
Show file tree
Hide file tree
Showing 13 changed files with 292 additions and 8 deletions.
13 changes: 11 additions & 2 deletions crates/api_models/src/events/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ use crate::{
PaymentListResponse, PaymentListResponseV2, PaymentsApproveRequest, PaymentsCancelRequest,
PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest,
PaymentsExternalAuthenticationRequest, PaymentsExternalAuthenticationResponse,
PaymentsIncrementalAuthorizationRequest, PaymentsRejectRequest, PaymentsRequest,
PaymentsResponse, PaymentsRetrieveRequest, PaymentsStartRequest, RedirectionResponse,
PaymentsIncrementalAuthorizationRequest, PaymentsManualUpdateRequest,
PaymentsRejectRequest, PaymentsRequest, PaymentsResponse, PaymentsRetrieveRequest,
PaymentsStartRequest, RedirectionResponse,
},
};
impl ApiEventMetric for PaymentsRetrieveRequest {
Expand Down Expand Up @@ -239,3 +240,11 @@ impl ApiEventMetric for PaymentsExternalAuthenticationRequest {
}

impl ApiEventMetric for ExtendedCardInfoResponse {}

impl ApiEventMetric for PaymentsManualUpdateRequest {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Some(ApiEventsType::Payment {
payment_id: self.payment_id.clone(),
})
}
}
19 changes: 19 additions & 0 deletions crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4653,6 +4653,25 @@ pub struct PaymentsExternalAuthenticationRequest {
pub threeds_method_comp_ind: ThreeDsCompletionIndicator,
}

#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)]
pub struct PaymentsManualUpdateRequest {
/// The identifier for the payment
#[serde(skip)]
pub payment_id: String,
/// The identifier for the payment attempt
pub attempt_id: String,
/// Merchant ID
pub merchant_id: String,
/// The status of the attempt
pub attempt_status: Option<enums::AttemptStatus>,
/// Error code of the connector
pub error_code: Option<String>,
/// Error message of the connector
pub error_message: Option<String>,
/// Error reason of the connector
pub error_reason: Option<String>,
}

#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)]
pub enum ThreeDsCompletionIndicator {
/// 3DS method successfully completed
Expand Down
27 changes: 27 additions & 0 deletions crates/diesel_models/src/payment_attempt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,15 @@ pub enum PaymentAttemptUpdate {
authentication_id: Option<String>,
updated_by: String,
},
ManualUpdate {
status: Option<storage_enums::AttemptStatus>,
error_code: Option<String>,
error_message: Option<String>,
error_reason: Option<String>,
updated_by: String,
unified_code: Option<String>,
unified_message: Option<String>,
},
}

#[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)]
Expand Down Expand Up @@ -884,6 +893,24 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
updated_by,
..Default::default()
},
PaymentAttemptUpdate::ManualUpdate {
status,
error_code,
error_message,
error_reason,
updated_by,
unified_code,
unified_message,
} => Self {
status,
error_code: error_code.map(Some),
error_message: error_message.map(Some),
error_reason: error_reason.map(Some),
updated_by,
unified_code: unified_code.map(Some),
unified_message: unified_message.map(Some),
..Default::default()
},
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions crates/diesel_models/src/payment_intent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ pub enum PaymentIntentUpdate {
CompleteAuthorizeUpdate {
shipping_address_id: Option<String>,
},
ManualUpdate {
status: Option<storage_enums::IntentStatus>,
updated_by: String,
},
}

#[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)]
Expand Down Expand Up @@ -508,6 +512,11 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
shipping_address_id,
..Default::default()
},
PaymentIntentUpdate::ManualUpdate { status, updated_by } => Self {
status,
updated_by,
..Default::default()
},
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,15 @@ pub enum PaymentAttemptUpdate {
authentication_id: Option<String>,
updated_by: String,
},
ManualUpdate {
status: Option<storage_enums::AttemptStatus>,
error_code: Option<String>,
error_message: Option<String>,
error_reason: Option<String>,
updated_by: String,
unified_code: Option<String>,
unified_message: Option<String>,
},
}

impl ForeignIDRef for PaymentAttempt {
Expand Down
13 changes: 13 additions & 0 deletions crates/hyperswitch_domain_models/src/payments/payment_intent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,10 @@ pub enum PaymentIntentUpdate {
CompleteAuthorizeUpdate {
shipping_address_id: Option<String>,
},
ManualUpdate {
status: Option<storage_enums::IntentStatus>,
updated_by: String,
},
}

#[derive(Clone, Debug, Default)]
Expand Down Expand Up @@ -442,6 +446,12 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
shipping_address_id,
..Default::default()
},
PaymentIntentUpdate::ManualUpdate { status, updated_by } => Self {
status,
modified_at: Some(common_utils::date_time::now()),
updated_by,
..Default::default()
},
}
}
}
Expand Down Expand Up @@ -611,6 +621,9 @@ impl From<PaymentIntentUpdate> for DieselPaymentIntentUpdate {
} => Self::CompleteAuthorizeUpdate {
shipping_address_id,
},
PaymentIntentUpdate::ManualUpdate { status, updated_by } => {
Self::ManualUpdate { status, updated_by }
}
}
}
}
Expand Down
112 changes: 112 additions & 0 deletions crates/router/src/core/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4134,3 +4134,115 @@ pub async fn get_extended_card_info(
payments_api::ExtendedCardInfoResponse { payload },
))
}

#[cfg(feature = "olap")]
pub async fn payments_manual_update(
state: SessionState,
req: api_models::payments::PaymentsManualUpdateRequest,
) -> RouterResponse<serde_json::Value> {
let api_models::payments::PaymentsManualUpdateRequest {
payment_id,
attempt_id,
merchant_id,
attempt_status,
error_code,
error_message,
error_reason,
} = req;
let key_store = state
.store
.get_merchant_key_store_by_merchant_id(
&merchant_id,
&state.store.get_master_key().to_vec().into(),
)
.await
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)
.attach_printable("Error while fetching the key store by merchant_id")?;
let merchant_account = state
.store
.find_merchant_account_by_merchant_id(&merchant_id, &key_store)
.await
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)
.attach_printable("Error while fetching the merchant_account by merchant_id")?;
let payment_attempt = state
.store
.find_payment_attempt_by_payment_id_merchant_id_attempt_id(
&payment_id,
&merchant_id,
&attempt_id.clone(),
merchant_account.storage_scheme,
)
.await
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
.attach_printable(
"Error while fetching the payment_attempt by payment_id, merchant_id and attempt_id",
)?;
let payment_intent = state
.store
.find_payment_intent_by_payment_id_merchant_id(
&payment_id,
&merchant_account.merchant_id,
&key_store,
merchant_account.storage_scheme,
)
.await
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
.attach_printable("Error while fetching the payment_intent by payment_id, merchant_id")?;
let option_gsm = if let Some(((code, message), connector_name)) = error_code
.as_ref()
.zip(error_message.as_ref())
.zip(payment_attempt.connector.as_ref())
{
helpers::get_gsm_record(
&state,
Some(code.to_string()),
Some(message.to_string()),
connector_name.to_string(),
// We need to get the unified_code and unified_message of the Authorize flow
"Authorize".to_string(),
)
.await
} else {
None
};
// Update the payment_attempt
let attempt_update = storage::PaymentAttemptUpdate::ManualUpdate {
status: attempt_status,
error_code,
error_message,
error_reason,
updated_by: merchant_account.storage_scheme.to_string(),
unified_code: option_gsm.as_ref().and_then(|gsm| gsm.unified_code.clone()),
unified_message: option_gsm.and_then(|gsm| gsm.unified_message),
};
let updated_payment_attempt = state
.store
.update_payment_attempt_with_attempt_id(
payment_attempt.clone(),
attempt_update,
merchant_account.storage_scheme,
)
.await
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
.attach_printable("Error while updating the payment_attempt")?;
// If the payment_attempt is active attempt for an intent, update the intent status
if payment_intent.active_attempt.get_id() == payment_attempt.attempt_id {
let intent_status = enums::IntentStatus::foreign_from(updated_payment_attempt.status);
let payment_intent_update = storage::PaymentIntentUpdate::ManualUpdate {
status: Some(intent_status),
updated_by: merchant_account.storage_scheme.to_string(),
};
state
.store
.update_payment_intent(
payment_intent,
payment_intent_update,
&key_store,
merchant_account.storage_scheme,
)
.await
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
.attach_printable("Error while updating payment_intent")?;
}
Ok(services::ApplicationResponse::StatusOk)
}
4 changes: 4 additions & 0 deletions crates/router/src/routes/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,10 @@ impl Payments {
)
.service(web::resource("/filter").route(web::post().to(get_filters_for_payments)))
.service(web::resource("/v2/filter").route(web::get().to(get_payment_filters)))
.service(
web::resource("/{payment_id}/manual-update")
.route(web::put().to(payments_manual_update)),
)
}
#[cfg(feature = "oltp")]
{
Expand Down
3 changes: 2 additions & 1 deletion crates/router/src/routes/lock_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ impl From<Flow> for ApiIdentifier {
| Flow::PaymentsExternalAuthentication
| Flow::PaymentsAuthorize
| Flow::GetExtendedCardInfo
| Flow::PaymentsCompleteAuthorize => Self::Payments,
| Flow::PaymentsCompleteAuthorize
| Flow::PaymentsManualUpdate => Self::Payments,

Flow::PayoutsCreate
| Flow::PayoutsRetrieve
Expand Down
45 changes: 45 additions & 0 deletions crates/router/src/routes/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1394,6 +1394,35 @@ pub async fn post_3ds_payments_authorize(
.await
}

#[cfg(feature = "olap")]
pub async fn payments_manual_update(
state: web::Data<app::AppState>,
req: actix_web::HttpRequest,
json_payload: web::Json<payment_types::PaymentsManualUpdateRequest>,
path: web::Path<String>,
) -> impl Responder {
let flow = Flow::PaymentsManualUpdate;
let mut payload = json_payload.into_inner();
let payment_id = path.into_inner();

let locking_action = payload.get_locking_input(flow.clone());

tracing::Span::current().record("payment_id", &payment_id);

payload.payment_id = payment_id;

Box::pin(api::server_wrap(
flow,
state,
&req,
payload,
|state, _auth, req, _req_state| payments::payments_manual_update(state, req),
&auth::AdminApiAuth,
locking_action,
))
.await
}

/// Retrieve endpoint for merchant to fetch the encrypted customer payment method data
#[instrument(skip_all, fields(flow = ?Flow::GetExtendedCardInfo, payment_id))]
pub async fn retrieve_extended_card_info(
Expand Down Expand Up @@ -1654,3 +1683,19 @@ impl GetLockingInput for payment_types::PaymentsExternalAuthenticationRequest {
}
}
}

impl GetLockingInput for payment_types::PaymentsManualUpdateRequest {
fn get_locking_input<F>(&self, flow: F) -> api_locking::LockAction
where
F: types::FlowMetric,
lock_utils::ApiIdentifier: From<F>,
{
api_locking::LockAction::Hold {
input: api_locking::LockingInput {
unique_locking_key: self.payment_id.to_owned(),
api_identifier: lock_utils::ApiIdentifier::from(flow),
override_lock_retries: None,
},
}
}
}
10 changes: 5 additions & 5 deletions crates/router/src/types/api/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ pub use api_models::payments::{
PaymentRetrieveBody, PaymentRetrieveBodyWithCredentials, PaymentsApproveRequest,
PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest,
PaymentsExternalAuthenticationRequest, PaymentsIncrementalAuthorizationRequest,
PaymentsRedirectRequest, PaymentsRedirectionResponse, PaymentsRejectRequest, PaymentsRequest,
PaymentsResponse, PaymentsResponseForm, PaymentsRetrieveRequest, PaymentsSessionRequest,
PaymentsSessionResponse, PaymentsStartRequest, PgRedirectResponse, PhoneDetails,
RedirectionResponse, SessionToken, TimeRange, UrlDetails, VerifyRequest, VerifyResponse,
WalletData,
PaymentsManualUpdateRequest, PaymentsRedirectRequest, PaymentsRedirectionResponse,
PaymentsRejectRequest, PaymentsRequest, PaymentsResponse, PaymentsResponseForm,
PaymentsRetrieveRequest, PaymentsSessionRequest, PaymentsSessionResponse, PaymentsStartRequest,
PgRedirectResponse, PhoneDetails, RedirectionResponse, SessionToken, TimeRange, UrlDetails,
VerifyRequest, VerifyResponse, WalletData,
};
use error_stack::ResultExt;
pub use hyperswitch_domain_models::router_flow_types::payments::{
Expand Down
2 changes: 2 additions & 0 deletions crates/router_env/src/logger/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,8 @@ pub enum Flow {
ToggleConnectorAgnosticMit,
/// Get the extended card info associated to a payment_id
GetExtendedCardInfo,
/// Manually update the payment details like status, error code, error message etc.
PaymentsManualUpdate,
}

///
Expand Down
Loading

0 comments on commit ed021c1

Please sign in to comment.