mirror of
https://github.com/juspay/hyperswitch.git
synced 2026-03-13 09:02:06 +08:00
refactor(relay): add manual multiple capture for relay and fix database deserialization (#11264)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@@ -35753,6 +35753,14 @@
|
||||
},
|
||||
"currency": {
|
||||
"$ref": "#/components/schemas/Currency"
|
||||
},
|
||||
"capture_method": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/CaptureMethod"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -54,6 +54,9 @@ pub struct RelayCaptureRequestData {
|
||||
/// The currency in which the amount is being captured
|
||||
#[schema(value_type = Currency)]
|
||||
pub currency: api_enums::Currency,
|
||||
/// type of capture for the relay
|
||||
#[schema(value_type = Option<CaptureMethod>)]
|
||||
pub capture_method: Option<api_enums::CaptureMethod>,
|
||||
}
|
||||
|
||||
#[derive(Debug, ToSchema, Clone, Deserialize, Serialize)]
|
||||
|
||||
@@ -2957,6 +2957,40 @@ pub enum RelayStatus {
|
||||
Failure,
|
||||
}
|
||||
|
||||
impl RelayStatus {
|
||||
pub fn get_void_status(attempt_status: AttemptStatus) -> Self {
|
||||
match attempt_status {
|
||||
AttemptStatus::Failure
|
||||
| AttemptStatus::AuthenticationFailed
|
||||
| AttemptStatus::RouterDeclined
|
||||
| AttemptStatus::AuthorizationFailed
|
||||
| AttemptStatus::CaptureFailed
|
||||
| AttemptStatus::VoidFailed
|
||||
| AttemptStatus::IntegrityFailure
|
||||
| AttemptStatus::AutoRefunded
|
||||
| AttemptStatus::Expired => Self::Failure,
|
||||
AttemptStatus::Pending
|
||||
| AttemptStatus::PaymentMethodAwaited
|
||||
| AttemptStatus::Authorized
|
||||
| AttemptStatus::PartiallyAuthorized
|
||||
| AttemptStatus::AuthenticationSuccessful
|
||||
| AttemptStatus::ConfirmationAwaited
|
||||
| AttemptStatus::DeviceDataCollectionPending
|
||||
| AttemptStatus::VoidInitiated
|
||||
| AttemptStatus::Unresolved
|
||||
| AttemptStatus::Charged
|
||||
| AttemptStatus::PartialChargedAndChargeable
|
||||
| AttemptStatus::CodInitiated
|
||||
| AttemptStatus::PartialCharged
|
||||
| AttemptStatus::Authorizing
|
||||
| AttemptStatus::CaptureInitiated
|
||||
| AttemptStatus::AuthenticationPending
|
||||
| AttemptStatus::Started => Self::Pending,
|
||||
AttemptStatus::Voided | AttemptStatus::VoidedPostCharge => Self::Success,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
|
||||
@@ -2,6 +2,7 @@ use common_enums::enums;
|
||||
use common_utils::{
|
||||
self,
|
||||
errors::{CustomResult, ValidationError},
|
||||
ext_traits::ValueExt,
|
||||
id_type::{self, GenerateId},
|
||||
pii,
|
||||
types::{keymanager, MinorUnit},
|
||||
@@ -77,6 +78,7 @@ impl From<api_models::relay::RelayData> for RelayData {
|
||||
authorized_amount: relay_capture_request.authorized_amount,
|
||||
amount_to_capture: relay_capture_request.amount_to_capture,
|
||||
currency: relay_capture_request.currency,
|
||||
capture_method: relay_capture_request.capture_method,
|
||||
})
|
||||
}
|
||||
api_models::relay::RelayData::IncrementalAuthorization(
|
||||
@@ -111,6 +113,7 @@ impl From<api_models::relay::RelayCaptureRequestData> for RelayCaptureData {
|
||||
authorized_amount: relay.authorized_amount,
|
||||
amount_to_capture: relay.amount_to_capture,
|
||||
currency: relay.currency,
|
||||
capture_method: relay.capture_method,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,11 +165,23 @@ impl RelayUpdate {
|
||||
),
|
||||
) -> CustomResult<Self, ApiErrorResponse> {
|
||||
match response {
|
||||
Err(error) => Ok(Self::ErrorUpdate {
|
||||
error_code: error.code,
|
||||
error_message: error.reason.unwrap_or(error.message),
|
||||
status: common_enums::RelayStatus::Failure,
|
||||
}),
|
||||
Err(error) => {
|
||||
let relay_status = common_enums::RelayStatus::from(status);
|
||||
|
||||
match relay_status {
|
||||
common_enums::RelayStatus::Failure => Ok(Self::ErrorUpdate {
|
||||
error_code: error.code,
|
||||
error_message: error.reason.unwrap_or(error.message),
|
||||
status: relay_status,
|
||||
}),
|
||||
common_enums::RelayStatus::Created
|
||||
| common_enums::RelayStatus::Pending
|
||||
| common_enums::RelayStatus::Success => Ok(Self::StatusUpdate {
|
||||
connector_reference_id: None,
|
||||
status: relay_status,
|
||||
}),
|
||||
}
|
||||
}
|
||||
Ok(response) => match response {
|
||||
router_response_types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id,
|
||||
@@ -262,7 +277,7 @@ impl RelayUpdate {
|
||||
..
|
||||
} => Ok(Self::StatusUpdate {
|
||||
connector_reference_id: resource_id.get_optional_response_id(),
|
||||
status: common_enums::RelayStatus::from(status),
|
||||
status: common_enums::RelayStatus::get_void_status(status),
|
||||
}),
|
||||
_ => Err(ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Payment Response Not Supported"),
|
||||
@@ -286,6 +301,7 @@ impl From<RelayData> for api_models::relay::RelayData {
|
||||
authorized_amount: relay_capture_request.authorized_amount,
|
||||
amount_to_capture: relay_capture_request.amount_to_capture,
|
||||
currency: relay_capture_request.currency,
|
||||
capture_method: relay_capture_request.capture_method,
|
||||
})
|
||||
}
|
||||
RelayData::IncrementalAuthorization(relay_incremental_authorization_request) => {
|
||||
@@ -334,6 +350,7 @@ impl From<Relay> for api_models::relay::RelayResponse {
|
||||
authorized_amount: relay_capture_request.authorized_amount,
|
||||
amount_to_capture: relay_capture_request.amount_to_capture,
|
||||
currency: relay_capture_request.currency,
|
||||
capture_method: relay_capture_request.capture_method,
|
||||
})
|
||||
}
|
||||
RelayData::IncrementalAuthorization(relay_incremental_authorization_request) => {
|
||||
@@ -378,6 +395,31 @@ pub enum RelayData {
|
||||
}
|
||||
|
||||
impl RelayData {
|
||||
pub fn parse_relay_data(
|
||||
value: Option<pii::SecretSerdeValue>,
|
||||
relay_type: enums::RelayType,
|
||||
) -> CustomResult<Option<Self>, ValidationError> {
|
||||
match value {
|
||||
Some(data) => match relay_type {
|
||||
enums::RelayType::Capture => Ok(Some(Self::Capture(RelayCaptureData::from_value(
|
||||
data.expose(),
|
||||
)?))),
|
||||
enums::RelayType::Refund => Ok(Some(Self::Refund(RelayRefundData::from_value(
|
||||
data.expose(),
|
||||
)?))),
|
||||
enums::RelayType::IncrementalAuthorization => {
|
||||
Ok(Some(Self::IncrementalAuthorization(
|
||||
RelayIncrementalAuthorizationData::from_value(data.expose())?,
|
||||
)))
|
||||
}
|
||||
enums::RelayType::Void => {
|
||||
Ok(Some(Self::Void(RelayVoidData::from_value(data.expose())?)))
|
||||
}
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_refund_data(&self) -> CustomResult<RelayRefundData, ApiErrorResponse> {
|
||||
match self.clone() {
|
||||
Self::Refund(refund_data) => Ok(refund_data),
|
||||
@@ -415,10 +457,10 @@ impl RelayData {
|
||||
pub fn get_void_data(&self) -> CustomResult<RelayVoidData, ApiErrorResponse> {
|
||||
match self.clone() {
|
||||
Self::Void(void_data) => Ok(void_data),
|
||||
Self::Refund(_) | Self::Capture(_) | Self::IncrementalAuthorization(_) => Err(
|
||||
ApiErrorResponse::InternalServerError,
|
||||
)
|
||||
.attach_printable("relay data does not contain relay incremental authorization data"),
|
||||
Self::Refund(_) | Self::Capture(_) | Self::IncrementalAuthorization(_) => {
|
||||
Err(ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("relay data does not contain relay void data")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -430,11 +472,32 @@ pub struct RelayRefundData {
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
impl RelayRefundData {
|
||||
pub fn from_value(value: serde_json::Value) -> CustomResult<Self, ValidationError> {
|
||||
value
|
||||
.parse_value("RelayRefundData")
|
||||
.change_context(ValidationError::InvalidValue {
|
||||
message: "Failed while deserializing RelayRefundData".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct RelayCaptureData {
|
||||
pub authorized_amount: MinorUnit,
|
||||
pub amount_to_capture: MinorUnit,
|
||||
pub currency: enums::Currency,
|
||||
pub capture_method: Option<enums::CaptureMethod>,
|
||||
}
|
||||
|
||||
impl RelayCaptureData {
|
||||
pub fn from_value(value: serde_json::Value) -> CustomResult<Self, ValidationError> {
|
||||
value
|
||||
.parse_value("RelayCaptureData")
|
||||
.change_context(ValidationError::InvalidValue {
|
||||
message: "Failed while deserializing RelayCaptureData".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
@@ -444,6 +507,16 @@ pub struct RelayIncrementalAuthorizationData {
|
||||
pub currency: enums::Currency,
|
||||
}
|
||||
|
||||
impl RelayIncrementalAuthorizationData {
|
||||
pub fn from_value(value: serde_json::Value) -> CustomResult<Self, ValidationError> {
|
||||
value
|
||||
.parse_value("RelayIncrementalAuthorizationData")
|
||||
.change_context(ValidationError::InvalidValue {
|
||||
message: "Failed while deserializing RelayIncrementalAuthorizationData".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct RelayVoidData {
|
||||
pub amount: Option<MinorUnit>,
|
||||
@@ -451,6 +524,16 @@ pub struct RelayVoidData {
|
||||
pub cancellation_reason: Option<String>,
|
||||
}
|
||||
|
||||
impl RelayVoidData {
|
||||
pub fn from_value(value: serde_json::Value) -> CustomResult<Self, ValidationError> {
|
||||
value
|
||||
.parse_value("RelayVoidData")
|
||||
.change_context(ValidationError::InvalidValue {
|
||||
message: "Failed while deserializing RelayVoidData".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RelayUpdate {
|
||||
ErrorUpdate {
|
||||
@@ -537,16 +620,7 @@ impl super::behaviour::Conversion for Relay {
|
||||
profile_id: item.profile_id,
|
||||
merchant_id: item.merchant_id,
|
||||
relay_type: item.relay_type,
|
||||
request_data: item
|
||||
.request_data
|
||||
.map(|data| {
|
||||
serde_json::from_value(data.expose()).change_context(
|
||||
ValidationError::InvalidValue {
|
||||
message: "Failed while decrypting business profile data".to_string(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.transpose()?,
|
||||
request_data: RelayData::parse_relay_data(item.request_data, item.relay_type)?,
|
||||
status: item.status,
|
||||
connector_reference_id: item.connector_reference_id,
|
||||
error_code: item.error_code,
|
||||
|
||||
@@ -12,9 +12,10 @@ use hyperswitch_domain_models::relay;
|
||||
|
||||
use super::errors::{self, ConnectorErrorExt, RouterResponse, RouterResult, StorageErrorExt};
|
||||
use crate::{
|
||||
connector::utils::RouterData,
|
||||
core::payments,
|
||||
routes::SessionState,
|
||||
services,
|
||||
services::{self, api::ConnectorValidation},
|
||||
types::{
|
||||
api::{self},
|
||||
domain,
|
||||
@@ -971,16 +972,34 @@ pub async fn sync_relay_capture_with_gateway(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let router_data_res = services::execute_connector_processing_step(
|
||||
state,
|
||||
connector_integration,
|
||||
&router_data,
|
||||
payments::CallConnectorAction::Trigger,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.to_payment_failed_response()?;
|
||||
//validate_psync_reference_id if call_connector_action is trigger
|
||||
let router_data_res = if connector_data
|
||||
.connector
|
||||
.validate_psync_reference_id(
|
||||
&router_data.request,
|
||||
router_data.is_three_ds(),
|
||||
router_data.status,
|
||||
router_data.connector_meta_data.clone(),
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
router_env::logger::warn!(
|
||||
"validate_psync_reference_id failed, hence skipping call to connector"
|
||||
);
|
||||
|
||||
router_data
|
||||
} else {
|
||||
services::execute_connector_processing_step(
|
||||
state,
|
||||
connector_integration,
|
||||
&router_data,
|
||||
payments::CallConnectorAction::Trigger,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.to_payment_failed_response()?
|
||||
};
|
||||
|
||||
let relay_response = relay::RelayUpdate::try_from_capture_response((
|
||||
router_data_res.status,
|
||||
|
||||
@@ -77,7 +77,7 @@ pub async fn construct_relay_refund_router_data<F>(
|
||||
connector: connector_name.to_string(),
|
||||
payment_id: IRRELEVANT_PAYMENT_INTENT_ID.to_string(),
|
||||
attempt_id: IRRELEVANT_PAYMENT_ATTEMPT_ID.to_string(),
|
||||
status: common_enums::AttemptStatus::Charged,
|
||||
status: common_enums::AttemptStatus::Pending,
|
||||
payment_method: common_enums::PaymentMethod::default(),
|
||||
payment_method_type: None,
|
||||
connector_auth_type,
|
||||
@@ -213,7 +213,7 @@ pub async fn construct_relay_capture_router_data(
|
||||
connector: connector_name.to_string(),
|
||||
payment_id: IRRELEVANT_PAYMENT_INTENT_ID.to_string(),
|
||||
attempt_id: IRRELEVANT_PAYMENT_ATTEMPT_ID.to_string(),
|
||||
status: common_enums::AttemptStatus::Charged,
|
||||
status: common_enums::AttemptStatus::Pending,
|
||||
payment_method: common_enums::PaymentMethod::default(),
|
||||
payment_method_type: None,
|
||||
connector_auth_type,
|
||||
@@ -230,7 +230,13 @@ pub async fn construct_relay_capture_router_data(
|
||||
currency: relay_capture_data.currency,
|
||||
connector_transaction_id: relay_record.connector_resource_id.clone(),
|
||||
payment_amount: relay_capture_data.authorized_amount.get_amount_as_i64(),
|
||||
multiple_capture_data: None,
|
||||
multiple_capture_data: Some(
|
||||
// for relay, each manual multiple capture is a separate entity i.e not related
|
||||
hyperswitch_domain_models::router_request_types::MultipleCaptureRequestData {
|
||||
capture_sequence: 1,
|
||||
capture_reference: relay_id_string.clone(),
|
||||
},
|
||||
),
|
||||
connector_meta: None,
|
||||
browser_info: None,
|
||||
metadata: None,
|
||||
@@ -340,7 +346,7 @@ pub async fn construct_relay_incremental_authorization_router_data(
|
||||
connector: connector_name.to_string(),
|
||||
payment_id: IRRELEVANT_PAYMENT_INTENT_ID.to_string(),
|
||||
attempt_id: IRRELEVANT_PAYMENT_ATTEMPT_ID.to_string(),
|
||||
status: common_enums::AttemptStatus::Charged,
|
||||
status: common_enums::AttemptStatus::Pending,
|
||||
payment_method: common_enums::PaymentMethod::default(),
|
||||
payment_method_type: None,
|
||||
connector_auth_type,
|
||||
@@ -466,7 +472,7 @@ pub async fn construct_relay_void_router_data(
|
||||
connector: connector_name.to_string(),
|
||||
payment_id: IRRELEVANT_PAYMENT_INTENT_ID.to_string(),
|
||||
attempt_id: IRRELEVANT_PAYMENT_ATTEMPT_ID.to_string(),
|
||||
status: common_enums::AttemptStatus::Charged,
|
||||
status: common_enums::AttemptStatus::Pending,
|
||||
payment_method: common_enums::PaymentMethod::default(),
|
||||
payment_method_type: None,
|
||||
connector_auth_type,
|
||||
@@ -488,7 +494,7 @@ pub async fn construct_relay_void_router_data(
|
||||
connector_meta: None,
|
||||
browser_info: None,
|
||||
metadata: None,
|
||||
minor_amount: None,
|
||||
minor_amount: relay_void_data.amount,
|
||||
webhook_url,
|
||||
capture_method: None,
|
||||
split_payments: None,
|
||||
@@ -609,7 +615,7 @@ pub async fn construct_relay_payments_retrieve_router_data(
|
||||
connector: connector_name.to_string(),
|
||||
payment_id: IRRELEVANT_PAYMENT_INTENT_ID.to_string(),
|
||||
attempt_id: IRRELEVANT_PAYMENT_ATTEMPT_ID.to_string(),
|
||||
status: common_enums::AttemptStatus::Charged,
|
||||
status: common_enums::AttemptStatus::Pending,
|
||||
payment_method: common_enums::PaymentMethod::default(),
|
||||
payment_method_type: None,
|
||||
connector_auth_type,
|
||||
|
||||
Reference in New Issue
Block a user