mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-26 19:04:36 +08:00
feat(router): added dispute retrieve and dispute list apis (#842)
Co-authored-by: Sangamesh <sangamesh.kulkarni@juspay.in> Co-authored-by: sai harsha <sai.harsha@sai.harsha-MacBookPro> Co-authored-by: Arun Raj M <jarnura47@gmail.com>
This commit is contained in:
committed by
GitHub
parent
d1d58e33b7
commit
acab7671b0
@ -1,4 +1,5 @@
|
||||
use masking::Serialize;
|
||||
use masking::{Deserialize, Serialize};
|
||||
use time::PrimitiveDateTime;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use super::enums::{DisputeStage, DisputeStatus};
|
||||
@ -19,6 +20,8 @@ pub struct DisputeResponse {
|
||||
pub dispute_stage: DisputeStage,
|
||||
/// Status of the dispute
|
||||
pub dispute_status: DisputeStatus,
|
||||
/// connector to which dispute is associated with
|
||||
pub connector: String,
|
||||
/// Status of the dispute sent by connector
|
||||
pub connector_status: String,
|
||||
/// Dispute id sent by connector
|
||||
@ -36,3 +39,37 @@ pub struct DisputeResponse {
|
||||
/// Time at which dispute is received
|
||||
pub received_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, ToSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct DisputeListConstraints {
|
||||
/// limit on the number of objects to return
|
||||
pub limit: Option<i64>,
|
||||
/// status of the dispute
|
||||
pub dispute_status: Option<DisputeStatus>,
|
||||
/// stage of the dispute
|
||||
pub dispute_stage: Option<DisputeStage>,
|
||||
/// reason for the dispute
|
||||
pub reason: Option<String>,
|
||||
/// connector linked to dispute
|
||||
pub connector: Option<String>,
|
||||
/// The time at which dispute is received
|
||||
#[schema(example = "2022-09-10T10:11:12Z")]
|
||||
pub received_time: Option<PrimitiveDateTime>,
|
||||
/// Time less than the dispute received time
|
||||
#[schema(example = "2022-09-10T10:11:12Z")]
|
||||
#[serde(rename = "received_time.lt")]
|
||||
pub received_time_lt: Option<PrimitiveDateTime>,
|
||||
/// Time greater than the dispute received time
|
||||
#[schema(example = "2022-09-10T10:11:12Z")]
|
||||
#[serde(rename = "received_time.gt")]
|
||||
pub received_time_gt: Option<PrimitiveDateTime>,
|
||||
/// Time less than or equals to the dispute received time
|
||||
#[schema(example = "2022-09-10T10:11:12Z")]
|
||||
#[serde(rename = "received_time.lte")]
|
||||
pub received_time_lte: Option<PrimitiveDateTime>,
|
||||
/// Time greater than or equals to the dispute received time
|
||||
#[schema(example = "2022-09-10T10:11:12Z")]
|
||||
#[serde(rename = "received_time.gte")]
|
||||
pub received_time_gte: Option<PrimitiveDateTime>,
|
||||
}
|
||||
|
||||
@ -180,6 +180,8 @@ pub enum StripeErrorCode {
|
||||
|
||||
#[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "The connector provided in the request is incorrect or not available")]
|
||||
IncorrectConnectorNameGiven,
|
||||
#[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "No such {object}: '{id}'")]
|
||||
ResourceMissing { object: String, id: String },
|
||||
// [#216]: https://github.com/juspay/hyperswitch/issues/216
|
||||
// Implement the remaining stripe error codes
|
||||
|
||||
@ -460,6 +462,10 @@ impl From<errors::ApiErrorResponse> for StripeErrorCode {
|
||||
errors::ApiErrorResponse::DuplicatePayment { payment_id } => {
|
||||
Self::DuplicatePayment { payment_id }
|
||||
}
|
||||
errors::ApiErrorResponse::DisputeNotFound { dispute_id } => Self::ResourceMissing {
|
||||
object: "dispute".to_owned(),
|
||||
id: dispute_id,
|
||||
},
|
||||
errors::ApiErrorResponse::NotSupported { .. } => Self::InternalServerError,
|
||||
}
|
||||
}
|
||||
@ -507,7 +513,8 @@ impl actix_web::ResponseError for StripeErrorCode {
|
||||
| Self::PaymentIntentMandateInvalid { .. }
|
||||
| Self::PaymentIntentUnexpectedState { .. }
|
||||
| Self::DuplicatePayment { .. }
|
||||
| Self::IncorrectConnectorNameGiven => StatusCode::BAD_REQUEST,
|
||||
| Self::IncorrectConnectorNameGiven
|
||||
| Self::ResourceMissing { .. } => StatusCode::BAD_REQUEST,
|
||||
Self::RefundFailed
|
||||
| Self::InternalServerError
|
||||
| Self::MandateActive
|
||||
|
||||
@ -3,6 +3,7 @@ pub mod api_keys;
|
||||
pub mod cards_info;
|
||||
pub mod configs;
|
||||
pub mod customers;
|
||||
pub mod disputes;
|
||||
pub mod errors;
|
||||
pub mod mandate;
|
||||
pub mod metrics;
|
||||
|
||||
47
crates/router/src/core/disputes.rs
Normal file
47
crates/router/src/core/disputes.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use router_env::{instrument, tracing};
|
||||
|
||||
use super::errors::{self, RouterResponse, StorageErrorExt};
|
||||
use crate::{
|
||||
routes::AppState,
|
||||
services,
|
||||
types::{api::disputes, storage, transformers::ForeignFrom},
|
||||
};
|
||||
|
||||
#[instrument(skip(state))]
|
||||
pub async fn retrieve_dispute(
|
||||
state: &AppState,
|
||||
merchant_account: storage::MerchantAccount,
|
||||
req: disputes::DisputeId,
|
||||
) -> RouterResponse<api_models::disputes::DisputeResponse> {
|
||||
let dispute = state
|
||||
.store
|
||||
.find_dispute_by_merchant_id_dispute_id(&merchant_account.merchant_id, &req.dispute_id)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
error.to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
|
||||
dispute_id: req.dispute_id,
|
||||
})
|
||||
})?;
|
||||
let dispute_response = api_models::disputes::DisputeResponse::foreign_from(dispute);
|
||||
Ok(services::ApplicationResponse::Json(dispute_response))
|
||||
}
|
||||
|
||||
#[instrument(skip(state))]
|
||||
pub async fn retrieve_disputes_list(
|
||||
state: &AppState,
|
||||
merchant_account: storage::MerchantAccount,
|
||||
constraints: api_models::disputes::DisputeListConstraints,
|
||||
) -> RouterResponse<Vec<api_models::disputes::DisputeResponse>> {
|
||||
let disputes = state
|
||||
.store
|
||||
.find_disputes_by_merchant_id(&merchant_account.merchant_id, constraints)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
error.to_not_found_response(errors::ApiErrorResponse::InternalServerError)
|
||||
})?;
|
||||
let disputes_list = disputes
|
||||
.into_iter()
|
||||
.map(api_models::disputes::DisputeResponse::foreign_from)
|
||||
.collect();
|
||||
Ok(services::ApplicationResponse::Json(disputes_list))
|
||||
}
|
||||
@ -158,6 +158,8 @@ pub enum ApiErrorResponse {
|
||||
IncorrectConnectorNameGiven,
|
||||
#[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "Address does not exist in our records")]
|
||||
AddressNotFound,
|
||||
#[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "Dispute does not exist in our records")]
|
||||
DisputeNotFound { dispute_id: String },
|
||||
#[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "Card with the provided iin does not exist")]
|
||||
InvalidCardIin,
|
||||
#[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "The provided card IIN length is invalid, please provide an iin with 6 or 8 digits")]
|
||||
@ -253,7 +255,8 @@ impl actix_web::ResponseError for ApiErrorResponse {
|
||||
Self::DuplicateMerchantAccount
|
||||
| Self::DuplicateMerchantConnectorAccount
|
||||
| Self::DuplicatePaymentMethod
|
||||
| Self::DuplicateMandate => StatusCode::BAD_REQUEST, // 400
|
||||
| Self::DuplicateMandate
|
||||
| Self::DisputeNotFound { .. } => StatusCode::BAD_REQUEST, // 400
|
||||
Self::ReturnUrlUnavailable => StatusCode::SERVICE_UNAVAILABLE, // 503
|
||||
Self::PaymentNotSucceeded => StatusCode::BAD_REQUEST, // 400
|
||||
Self::NotImplemented { .. } => StatusCode::NOT_IMPLEMENTED, // 501
|
||||
@ -444,6 +447,9 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
|
||||
Self::FlowNotSupported { flow, connector } => {
|
||||
AER::BadRequest(ApiError::new("IR", 20, format!("{flow} flow not supported"), Some(Extra {connector: Some(connector.to_owned()), ..Default::default()}))) //FIXME: error message
|
||||
},
|
||||
Self::DisputeNotFound { .. } => {
|
||||
AER::NotFound(ApiError::new("HE", 2, "Dispute does not exist in our records", None))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -228,9 +228,9 @@ async fn get_or_update_dispute_object(
|
||||
option_dispute: Option<storage_models::dispute::Dispute>,
|
||||
dispute_details: api::disputes::DisputePayload,
|
||||
merchant_id: &str,
|
||||
payment_id: &str,
|
||||
attempt_id: &str,
|
||||
payment_attempt: &storage_models::payment_attempt::PaymentAttempt,
|
||||
event_type: api_models::webhooks::IncomingWebhookEvent,
|
||||
connector_name: &str,
|
||||
) -> CustomResult<storage_models::dispute::Dispute, errors::WebhooksFlowError> {
|
||||
let db = &*state.store;
|
||||
match option_dispute {
|
||||
@ -246,8 +246,9 @@ async fn get_or_update_dispute_object(
|
||||
.foreign_try_into()
|
||||
.into_report()
|
||||
.change_context(errors::WebhooksFlowError::DisputeCoreFailed)?,
|
||||
payment_id: payment_id.to_owned(),
|
||||
attempt_id: attempt_id.to_owned(),
|
||||
payment_id: payment_attempt.payment_id.to_owned(),
|
||||
connector: connector_name.to_owned(),
|
||||
attempt_id: payment_attempt.attempt_id.to_owned(),
|
||||
merchant_id: merchant_id.to_owned(),
|
||||
connector_status: dispute_details.connector_status,
|
||||
connector_dispute_id: dispute_details.connector_dispute_id,
|
||||
@ -327,18 +328,12 @@ async fn disputes_incoming_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
option_dispute,
|
||||
dispute_details,
|
||||
&merchant_account.merchant_id,
|
||||
&payment_attempt.payment_id,
|
||||
&payment_attempt.attempt_id,
|
||||
&payment_attempt,
|
||||
event_type.clone(),
|
||||
connector.id(),
|
||||
)
|
||||
.await?;
|
||||
let disputes_response = Box::new(
|
||||
dispute_object
|
||||
.clone()
|
||||
.foreign_try_into()
|
||||
.into_report()
|
||||
.change_context(errors::WebhooksFlowError::DisputeCoreFailed)?,
|
||||
);
|
||||
let disputes_response = Box::new(dispute_object.clone().foreign_into());
|
||||
let event_type: enums::EventType = dispute_object
|
||||
.dispute_status
|
||||
.foreign_try_into()
|
||||
|
||||
@ -4,7 +4,7 @@ use super::{MockDb, Store};
|
||||
use crate::{
|
||||
connection,
|
||||
core::errors::{self, CustomResult},
|
||||
types::storage,
|
||||
types::storage::{self, DisputeDbExt},
|
||||
};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@ -21,6 +21,18 @@ pub trait DisputeInterface {
|
||||
connector_dispute_id: &str,
|
||||
) -> CustomResult<Option<storage::Dispute>, errors::StorageError>;
|
||||
|
||||
async fn find_dispute_by_merchant_id_dispute_id(
|
||||
&self,
|
||||
merchant_id: &str,
|
||||
dispute_id: &str,
|
||||
) -> CustomResult<storage::Dispute, errors::StorageError>;
|
||||
|
||||
async fn find_disputes_by_merchant_id(
|
||||
&self,
|
||||
merchant_id: &str,
|
||||
dispute_constraints: api_models::disputes::DisputeListConstraints,
|
||||
) -> CustomResult<Vec<storage::Dispute>, errors::StorageError>;
|
||||
|
||||
async fn update_dispute(
|
||||
&self,
|
||||
this: storage::Dispute,
|
||||
@ -60,6 +72,30 @@ impl DisputeInterface for Store {
|
||||
.into_report()
|
||||
}
|
||||
|
||||
async fn find_dispute_by_merchant_id_dispute_id(
|
||||
&self,
|
||||
merchant_id: &str,
|
||||
dispute_id: &str,
|
||||
) -> CustomResult<storage::Dispute, errors::StorageError> {
|
||||
let conn = connection::pg_connection_read(self).await?;
|
||||
storage::Dispute::find_by_merchant_id_dispute_id(&conn, merchant_id, dispute_id)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.into_report()
|
||||
}
|
||||
|
||||
async fn find_disputes_by_merchant_id(
|
||||
&self,
|
||||
merchant_id: &str,
|
||||
dispute_constraints: api_models::disputes::DisputeListConstraints,
|
||||
) -> CustomResult<Vec<storage::Dispute>, errors::StorageError> {
|
||||
let conn = connection::pg_connection_read(self).await?;
|
||||
storage::Dispute::filter_by_constraints(&conn, merchant_id, dispute_constraints)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.into_report()
|
||||
}
|
||||
|
||||
async fn update_dispute(
|
||||
&self,
|
||||
this: storage::Dispute,
|
||||
@ -92,6 +128,24 @@ impl DisputeInterface for MockDb {
|
||||
Err(errors::StorageError::MockDbError)?
|
||||
}
|
||||
|
||||
async fn find_dispute_by_merchant_id_dispute_id(
|
||||
&self,
|
||||
_merchant_id: &str,
|
||||
_dispute_id: &str,
|
||||
) -> CustomResult<storage::Dispute, errors::StorageError> {
|
||||
// TODO: Implement function for `MockDb`
|
||||
Err(errors::StorageError::MockDbError)?
|
||||
}
|
||||
|
||||
async fn find_disputes_by_merchant_id(
|
||||
&self,
|
||||
_merchant_id: &str,
|
||||
_dispute_constraints: api_models::disputes::DisputeListConstraints,
|
||||
) -> CustomResult<Vec<storage::Dispute>, errors::StorageError> {
|
||||
// TODO: Implement function for `MockDb`
|
||||
Err(errors::StorageError::MockDbError)?
|
||||
}
|
||||
|
||||
async fn update_dispute(
|
||||
&self,
|
||||
_this: storage::Dispute,
|
||||
|
||||
@ -115,7 +115,8 @@ pub fn mk_app(
|
||||
{
|
||||
server_app = server_app
|
||||
.service(routes::MerchantAccount::server(state.clone()))
|
||||
.service(routes::ApiKeys::server(state.clone()));
|
||||
.service(routes::ApiKeys::server(state.clone()))
|
||||
.service(routes::Disputes::server(state.clone()));
|
||||
}
|
||||
|
||||
#[cfg(feature = "stripe")]
|
||||
|
||||
@ -57,6 +57,7 @@ Never share your secret api keys. Keep them guarded and secure.
|
||||
(name = "Mandates", description = "Manage mandates"),
|
||||
(name = "Customers", description = "Create and manage customers"),
|
||||
(name = "Payment Methods", description = "Create and manage payment methods of customers"),
|
||||
(name = "Disputes", description = "Manage disputes"),
|
||||
// (name = "API Key", description = "Create and manage API Keys"),
|
||||
),
|
||||
paths(
|
||||
@ -100,6 +101,8 @@ Never share your secret api keys. Keep them guarded and secure.
|
||||
// crate::routes::api_keys::api_key_update,
|
||||
// crate::routes::api_keys::api_key_revoke,
|
||||
// crate::routes::api_keys::api_key_list,
|
||||
crate::routes::disputes::retrieve_disputes_list,
|
||||
crate::routes::disputes::retrieve_dispute,
|
||||
),
|
||||
components(schemas(
|
||||
crate::types::api::refunds::RefundRequest,
|
||||
@ -143,9 +146,12 @@ Never share your secret api keys. Keep them guarded and secure.
|
||||
api_models::enums::PaymentExperience,
|
||||
api_models::enums::BankNames,
|
||||
api_models::enums::CardNetwork,
|
||||
api_models::enums::DisputeStage,
|
||||
api_models::enums::DisputeStatus,
|
||||
api_models::enums::CountryCode,
|
||||
api_models::admin::MerchantConnector,
|
||||
api_models::admin::PaymentMethodsEnabled,
|
||||
api_models::disputes::DisputeResponse,
|
||||
api_models::payments::AddressDetails,
|
||||
api_models::payments::Address,
|
||||
api_models::payments::BankRedirectData,
|
||||
|
||||
@ -4,6 +4,7 @@ pub mod app;
|
||||
pub mod cards_info;
|
||||
pub mod configs;
|
||||
pub mod customers;
|
||||
pub mod disputes;
|
||||
pub mod ephemeral_key;
|
||||
pub mod health;
|
||||
pub mod mandates;
|
||||
@ -15,8 +16,9 @@ pub mod refunds;
|
||||
pub mod webhooks;
|
||||
|
||||
pub use self::app::{
|
||||
ApiKeys, AppState, Cards, Configs, Customers, EphemeralKey, Health, Mandates, MerchantAccount,
|
||||
MerchantConnectorAccount, PaymentMethods, Payments, Payouts, Refunds, Webhooks,
|
||||
ApiKeys, AppState, Cards, Configs, Customers, Disputes, EphemeralKey, Health, Mandates,
|
||||
MerchantAccount, MerchantConnectorAccount, PaymentMethods, Payments, Payouts, Refunds,
|
||||
Webhooks,
|
||||
};
|
||||
#[cfg(feature = "stripe")]
|
||||
pub use super::compatibility::stripe::StripeApis;
|
||||
|
||||
@ -2,7 +2,7 @@ use actix_web::{web, Scope};
|
||||
|
||||
use super::health::*;
|
||||
#[cfg(feature = "olap")]
|
||||
use super::{admin::*, api_keys::*};
|
||||
use super::{admin::*, api_keys::*, disputes::*};
|
||||
#[cfg(any(feature = "olap", feature = "oltp"))]
|
||||
use super::{configs::*, customers::*, mandates::*, payments::*, payouts::*, refunds::*};
|
||||
#[cfg(feature = "oltp")]
|
||||
@ -379,6 +379,18 @@ impl ApiKeys {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Disputes;
|
||||
|
||||
#[cfg(feature = "olap")]
|
||||
impl Disputes {
|
||||
pub fn server(state: AppState) -> Scope {
|
||||
web::scope("/disputes")
|
||||
.app_data(web::Data::new(state))
|
||||
.service(web::resource("/list").route(web::get().to(retrieve_disputes_list)))
|
||||
.service(web::resource("/{dispute_id}").route(web::get().to(retrieve_dispute)))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Cards;
|
||||
|
||||
impl Cards {
|
||||
|
||||
89
crates/router/src/routes/disputes.rs
Normal file
89
crates/router/src/routes/disputes.rs
Normal file
@ -0,0 +1,89 @@
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use api_models::disputes::DisputeListConstraints;
|
||||
use router_env::{instrument, tracing, Flow};
|
||||
|
||||
use super::app::AppState;
|
||||
use crate::{
|
||||
core::disputes,
|
||||
services::{api, authentication as auth},
|
||||
types::api::disputes as dispute_types,
|
||||
};
|
||||
|
||||
/// Diputes - Retrieve Dispute
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/disputes/{dispute_id}",
|
||||
params(
|
||||
("dispute_id" = String, Path, description = "The identifier for dispute")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "The dispute was retrieved successfully", body = DisputeResponse),
|
||||
(status = 404, description = "Dispute does not exist in our records")
|
||||
),
|
||||
tag = "Disputes",
|
||||
operation_id = "Retrieve a Dispute",
|
||||
security(("api_key" = []))
|
||||
)]
|
||||
#[instrument(skip_all, fields(flow = ?Flow::DisputesRetrieve))]
|
||||
pub async fn retrieve_dispute(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
path: web::Path<String>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::DisputesRetrieve;
|
||||
let dispute_id = dispute_types::DisputeId {
|
||||
dispute_id: path.into_inner(),
|
||||
};
|
||||
api::server_wrap(
|
||||
flow,
|
||||
state.get_ref(),
|
||||
&req,
|
||||
dispute_id,
|
||||
disputes::retrieve_dispute,
|
||||
auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Diputes - List Disputes
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/disputes/list",
|
||||
params(
|
||||
("limit" = Option<i64>, Query, description = "The maximum number of Dispute Objects to include in the response"),
|
||||
("dispute_status" = Option<DisputeStatus>, Query, description = "The status of dispute"),
|
||||
("dispute_stage" = Option<DisputeStage>, Query, description = "The stage of dispute"),
|
||||
("reason" = Option<String>, Query, description = "The reason for dispute"),
|
||||
("connector" = Option<String>, Query, description = "The connector linked to dispute"),
|
||||
("received_time" = Option<PrimitiveDateTime>, Query, description = "The time at which dispute is received"),
|
||||
("received_time.lt" = Option<PrimitiveDateTime>, Query, description = "Time less than the dispute received time"),
|
||||
("received_time.gt" = Option<PrimitiveDateTime>, Query, description = "Time greater than the dispute received time"),
|
||||
("received_time.lte" = Option<PrimitiveDateTime>, Query, description = "Time less than or equals to the dispute received time"),
|
||||
("received_time.gte" = Option<PrimitiveDateTime>, Query, description = "Time greater than or equals to the dispute received time"),
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "The dispute list was retrieved successfully", body = Vec<DisputeResponse>),
|
||||
(status = 401, description = "Unauthorized request")
|
||||
),
|
||||
tag = "Disputes",
|
||||
operation_id = "List Disputes",
|
||||
security(("api_key" = []))
|
||||
)]
|
||||
#[instrument(skip_all, fields(flow = ?Flow::DisputesList))]
|
||||
pub async fn retrieve_disputes_list(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
payload: web::Query<DisputeListConstraints>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::DisputesList;
|
||||
let payload = payload.into_inner();
|
||||
api::server_wrap(
|
||||
flow,
|
||||
state.get_ref(),
|
||||
&req,
|
||||
payload,
|
||||
disputes::retrieve_disputes_list,
|
||||
auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@ -391,3 +391,16 @@ pub fn strip_jwt_token(token: &str) -> RouterResult<&str> {
|
||||
.strip_prefix("Bearer ")
|
||||
.ok_or_else(|| errors::ApiErrorResponse::InvalidJwtToken.into())
|
||||
}
|
||||
|
||||
pub fn auth_type<'a, T, A>(
|
||||
default_auth: &'a dyn AuthenticateAndFetch<T, A>,
|
||||
jwt_auth_type: &'a dyn AuthenticateAndFetch<T, A>,
|
||||
headers: &HeaderMap,
|
||||
) -> &'a dyn AuthenticateAndFetch<T, A>
|
||||
where
|
||||
{
|
||||
if is_jwt_auth(headers) {
|
||||
return jwt_auth_type;
|
||||
}
|
||||
default_auth
|
||||
}
|
||||
|
||||
@ -15,8 +15,8 @@ use std::{fmt::Debug, str::FromStr};
|
||||
use error_stack::{report, IntoReport, ResultExt};
|
||||
|
||||
pub use self::{
|
||||
admin::*, api_keys::*, configs::*, customers::*, payment_methods::*, payments::*, refunds::*,
|
||||
webhooks::*,
|
||||
admin::*, api_keys::*, configs::*, customers::*, disputes::*, payment_methods::*, payments::*,
|
||||
refunds::*, webhooks::*,
|
||||
};
|
||||
use super::ErrorResponse;
|
||||
use crate::{
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
use masking::Deserialize;
|
||||
use masking::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||
pub struct DisputeId {
|
||||
pub dispute_id: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
pub struct DisputePayload {
|
||||
|
||||
@ -1 +1,75 @@
|
||||
use async_bb8_diesel::AsyncRunQueryDsl;
|
||||
use common_utils::errors::CustomResult;
|
||||
use diesel::{associations::HasTable, ExpressionMethods, QueryDsl};
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
pub use storage_models::dispute::{Dispute, DisputeNew, DisputeUpdate};
|
||||
use storage_models::{errors, schema::dispute::dsl};
|
||||
|
||||
use crate::{connection::PgPooledConn, logger, types::transformers::ForeignInto};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait DisputeDbExt: Sized {
|
||||
async fn filter_by_constraints(
|
||||
conn: &PgPooledConn,
|
||||
merchant_id: &str,
|
||||
dispute_list_constraints: api_models::disputes::DisputeListConstraints,
|
||||
) -> CustomResult<Vec<Self>, errors::DatabaseError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl DisputeDbExt for Dispute {
|
||||
async fn filter_by_constraints(
|
||||
conn: &PgPooledConn,
|
||||
merchant_id: &str,
|
||||
dispute_list_constraints: api_models::disputes::DisputeListConstraints,
|
||||
) -> CustomResult<Vec<Self>, errors::DatabaseError> {
|
||||
let mut filter = <Self as HasTable>::table()
|
||||
.filter(dsl::merchant_id.eq(merchant_id.to_owned()))
|
||||
.order(dsl::modified_at.desc())
|
||||
.into_boxed();
|
||||
|
||||
if let Some(received_time) = dispute_list_constraints.received_time {
|
||||
filter = filter.filter(dsl::created_at.eq(received_time));
|
||||
}
|
||||
if let Some(received_time_lt) = dispute_list_constraints.received_time_lt {
|
||||
filter = filter.filter(dsl::created_at.lt(received_time_lt));
|
||||
}
|
||||
if let Some(received_time_gt) = dispute_list_constraints.received_time_gt {
|
||||
filter = filter.filter(dsl::created_at.gt(received_time_gt));
|
||||
}
|
||||
if let Some(received_time_lte) = dispute_list_constraints.received_time_lte {
|
||||
filter = filter.filter(dsl::created_at.le(received_time_lte));
|
||||
}
|
||||
if let Some(received_time_gte) = dispute_list_constraints.received_time_gte {
|
||||
filter = filter.filter(dsl::created_at.ge(received_time_gte));
|
||||
}
|
||||
if let Some(connector) = dispute_list_constraints.connector {
|
||||
filter = filter.filter(dsl::connector.eq(connector));
|
||||
}
|
||||
if let Some(reason) = dispute_list_constraints.reason {
|
||||
filter = filter.filter(dsl::connector_reason.eq(reason));
|
||||
}
|
||||
if let Some(dispute_stage) = dispute_list_constraints.dispute_stage {
|
||||
let storage_dispute_stage: storage_models::enums::DisputeStage =
|
||||
dispute_stage.foreign_into();
|
||||
filter = filter.filter(dsl::dispute_stage.eq(storage_dispute_stage));
|
||||
}
|
||||
if let Some(dispute_status) = dispute_list_constraints.dispute_status {
|
||||
let storage_dispute_status: storage_models::enums::DisputeStatus =
|
||||
dispute_status.foreign_into();
|
||||
filter = filter.filter(dsl::dispute_status.eq(storage_dispute_status));
|
||||
}
|
||||
if let Some(limit) = dispute_list_constraints.limit {
|
||||
filter = filter.limit(limit);
|
||||
}
|
||||
|
||||
logger::debug!(query = %diesel::debug_query::<diesel::pg::Pg, _>(&filter).to_string());
|
||||
|
||||
filter
|
||||
.get_results_async(conn)
|
||||
.await
|
||||
.into_report()
|
||||
.change_context(errors::DatabaseError::NotFound)
|
||||
.attach_printable_lazy(|| "Error filtering records by predicate")
|
||||
}
|
||||
}
|
||||
|
||||
@ -514,11 +514,9 @@ impl ForeignTryFrom<api_models::webhooks::IncomingWebhookEvent> for storage_enum
|
||||
}
|
||||
}
|
||||
|
||||
impl ForeignTryFrom<storage::Dispute> for api_models::disputes::DisputeResponse {
|
||||
type Error = errors::ValidationError;
|
||||
|
||||
fn foreign_try_from(dispute: storage::Dispute) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
impl ForeignFrom<storage::Dispute> for api_models::disputes::DisputeResponse {
|
||||
fn foreign_from(dispute: storage::Dispute) -> Self {
|
||||
Self {
|
||||
dispute_id: dispute.dispute_id,
|
||||
payment_id: dispute.payment_id,
|
||||
attempt_id: dispute.attempt_id,
|
||||
@ -526,6 +524,7 @@ impl ForeignTryFrom<storage::Dispute> for api_models::disputes::DisputeResponse
|
||||
currency: dispute.currency,
|
||||
dispute_stage: dispute.dispute_stage.foreign_into(),
|
||||
dispute_status: dispute.dispute_status.foreign_into(),
|
||||
connector: dispute.connector,
|
||||
connector_status: dispute.connector_status,
|
||||
connector_dispute_id: dispute.connector_dispute_id,
|
||||
connector_reason: dispute.connector_reason,
|
||||
@ -534,7 +533,7 @@ impl ForeignTryFrom<storage::Dispute> for api_models::disputes::DisputeResponse
|
||||
created_at: dispute.dispute_created_at,
|
||||
updated_at: dispute.updated_at,
|
||||
received_at: dispute.created_at.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -162,6 +162,10 @@ pub enum Flow {
|
||||
ApiKeyRevoke,
|
||||
/// API Key list flow
|
||||
ApiKeyList,
|
||||
/// Dispute Retrieve flow
|
||||
DisputesRetrieve,
|
||||
/// Dispute List flow
|
||||
DisputesList,
|
||||
/// Cards Info flow
|
||||
CardsInfo,
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ pub struct DisputeNew {
|
||||
pub payment_id: String,
|
||||
pub attempt_id: String,
|
||||
pub merchant_id: String,
|
||||
pub connector: String,
|
||||
pub connector_status: String,
|
||||
pub connector_dispute_id: String,
|
||||
pub connector_reason: Option<String>,
|
||||
@ -50,6 +51,7 @@ pub struct Dispute {
|
||||
pub created_at: PrimitiveDateTime,
|
||||
#[serde(with = "custom_serde::iso8601")]
|
||||
pub modified_at: PrimitiveDateTime,
|
||||
pub connector: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@ -34,6 +34,20 @@ impl Dispute {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_merchant_id_dispute_id(
|
||||
conn: &PgPooledConn,
|
||||
merchant_id: &str,
|
||||
dispute_id: &str,
|
||||
) -> StorageResult<Self> {
|
||||
generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
|
||||
conn,
|
||||
dsl::merchant_id
|
||||
.eq(merchant_id.to_owned())
|
||||
.and(dsl::dispute_id.eq(dispute_id.to_owned())),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(skip(conn))]
|
||||
pub async fn update(self, conn: &PgPooledConn, dispute: DisputeUpdate) -> StorageResult<Self> {
|
||||
match generics::generic_update_with_unique_predicate_get_result::<
|
||||
|
||||
@ -132,6 +132,7 @@ diesel::table! {
|
||||
updated_at -> Nullable<Varchar>,
|
||||
created_at -> Timestamp,
|
||||
modified_at -> Timestamp,
|
||||
connector -> Varchar,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ CREATE TABLE dispute (
|
||||
modified_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX dispute_id_index ON dispute (dispute_id);
|
||||
CREATE UNIQUE INDEX merchant_id_dispute_id_index ON dispute (merchant_id, dispute_id);
|
||||
|
||||
CREATE UNIQUE INDEX merchant_id_payment_id_connector_dispute_id_index ON dispute (merchant_id, payment_id, connector_dispute_id);
|
||||
|
||||
|
||||
@ -0,0 +1 @@
|
||||
ALTER TABLE dispute DROP COLUMN connector;
|
||||
@ -0,0 +1,3 @@
|
||||
-- Your SQL goes here
|
||||
ALTER TABLE dispute
|
||||
ADD COLUMN connector VARCHAR(255) NOT NULL;
|
||||
Reference in New Issue
Block a user