mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 19:46:48 +08:00
feat: list of refunds (#284)
This commit is contained in:
committed by
GitHub
parent
38649130bb
commit
e5330528fa
@ -1,4 +1,6 @@
|
|||||||
|
use common_utils::custom_serde;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use time::PrimitiveDateTime;
|
||||||
|
|
||||||
use crate::enums;
|
use crate::enums;
|
||||||
|
|
||||||
@ -34,6 +36,35 @@ pub struct RefundResponse {
|
|||||||
pub error_message: Option<String>,
|
pub error_message: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct RefundListRequest {
|
||||||
|
pub payment_id: Option<String>,
|
||||||
|
pub limit: Option<i64>,
|
||||||
|
#[serde(default, with = "custom_serde::iso8601::option")]
|
||||||
|
pub created: Option<PrimitiveDateTime>,
|
||||||
|
#[serde(default, rename = "created.lt", with = "custom_serde::iso8601::option")]
|
||||||
|
pub created_lt: Option<PrimitiveDateTime>,
|
||||||
|
#[serde(default, rename = "created.gt", with = "custom_serde::iso8601::option")]
|
||||||
|
pub created_gt: Option<PrimitiveDateTime>,
|
||||||
|
#[serde(
|
||||||
|
default,
|
||||||
|
rename = "created.lte",
|
||||||
|
with = "custom_serde::iso8601::option"
|
||||||
|
)]
|
||||||
|
pub created_lte: Option<PrimitiveDateTime>,
|
||||||
|
#[serde(
|
||||||
|
default,
|
||||||
|
rename = "created.gte",
|
||||||
|
with = "custom_serde::iso8601::option"
|
||||||
|
)]
|
||||||
|
pub created_gte: Option<PrimitiveDateTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct RefundListResponse {
|
||||||
|
pub data: Vec<RefundResponse>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, Clone, PartialEq, Default, Deserialize, Serialize)]
|
#[derive(Debug, Eq, Clone, PartialEq, Default, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum RefundStatus {
|
pub enum RefundStatus {
|
||||||
|
|||||||
@ -455,6 +455,40 @@ pub async fn validate_and_create_refund(
|
|||||||
Ok(refund.foreign_into())
|
Ok(refund.foreign_into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ********************************************** Refund list **********************************************
|
||||||
|
|
||||||
|
/// If payment-id is provided, lists all the refunds associated with that particular payment-id
|
||||||
|
/// If payment-id is not provided, lists the refunds associated with that particular merchant - to the limit specified,if no limits given, it is 10 by default
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub async fn refund_list(
|
||||||
|
db: &dyn db::StorageInterface,
|
||||||
|
merchant_account: storage::merchant_account::MerchantAccount,
|
||||||
|
req: api_models::refunds::RefundListRequest,
|
||||||
|
) -> RouterResponse<api_models::refunds::RefundListResponse> {
|
||||||
|
let limit = validator::validate_refund_list(req.limit)?;
|
||||||
|
let refund_list = db
|
||||||
|
.filter_refund_by_constraints(
|
||||||
|
&merchant_account.merchant_id,
|
||||||
|
&req,
|
||||||
|
merchant_account.storage_scheme,
|
||||||
|
limit,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.change_context(errors::ApiErrorResponse::RefundNotFound)?;
|
||||||
|
|
||||||
|
let data: Vec<refunds::RefundResponse> = refund_list
|
||||||
|
.into_iter()
|
||||||
|
.map(ForeignInto::foreign_into)
|
||||||
|
.collect();
|
||||||
|
utils::when(data.is_empty(), || {
|
||||||
|
Err(errors::ApiErrorResponse::RefundNotFound)
|
||||||
|
})?;
|
||||||
|
Ok(services::BachResponse::Json(
|
||||||
|
api_models::refunds::RefundListResponse { data },
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
// ********************************************** UTILS **********************************************
|
// ********************************************** UTILS **********************************************
|
||||||
|
|
||||||
// FIXME: function should not have more than 3 arguments.
|
// FIXME: function should not have more than 3 arguments.
|
||||||
|
|||||||
@ -122,3 +122,19 @@ pub async fn validate_uniqueness_of_refund_id_against_merchant_id(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn validate_refund_list(limit: Option<i64>) -> CustomResult<i64, errors::ApiErrorResponse> {
|
||||||
|
match limit {
|
||||||
|
Some(limit_val) => {
|
||||||
|
if !(1..=100).contains(&limit_val) {
|
||||||
|
Err(errors::ApiErrorResponse::InvalidRequestData {
|
||||||
|
message: "limit should be in between 1 and 100".to_string(),
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
} else {
|
||||||
|
Ok(limit_val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Ok(10),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -55,6 +55,14 @@ pub trait RefundInterface {
|
|||||||
new: storage_types::RefundNew,
|
new: storage_types::RefundNew,
|
||||||
storage_scheme: enums::MerchantStorageScheme,
|
storage_scheme: enums::MerchantStorageScheme,
|
||||||
) -> CustomResult<storage_types::Refund, errors::StorageError>;
|
) -> CustomResult<storage_types::Refund, errors::StorageError>;
|
||||||
|
|
||||||
|
async fn filter_refund_by_constraints(
|
||||||
|
&self,
|
||||||
|
merchant_id: &str,
|
||||||
|
refund_details: &api_models::refunds::RefundListRequest,
|
||||||
|
storage_scheme: enums::MerchantStorageScheme,
|
||||||
|
limit: i64,
|
||||||
|
) -> CustomResult<Vec<storage_models::refund::Refund>, errors::StorageError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "kv_store"))]
|
#[cfg(not(feature = "kv_store"))]
|
||||||
@ -163,6 +171,25 @@ mod storage {
|
|||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
.into_report()
|
.into_report()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn filter_refund_by_constraints(
|
||||||
|
&self,
|
||||||
|
merchant_id: &str,
|
||||||
|
refund_details: &api_models::refunds::RefundListRequest,
|
||||||
|
_storage_scheme: enums::MerchantStorageScheme,
|
||||||
|
limit: i64,
|
||||||
|
) -> CustomResult<Vec<storage_models::refund::Refund>, errors::StorageError> {
|
||||||
|
let conn = pg_connection(&self.master_pool).await;
|
||||||
|
<storage_models::refund::Refund as storage_types::RefundDbExt>::filter_by_constraints(
|
||||||
|
&conn,
|
||||||
|
merchant_id,
|
||||||
|
refund_details,
|
||||||
|
limit,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
.into_report()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -546,6 +573,26 @@ mod storage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn filter_refund_by_constraints(
|
||||||
|
&self,
|
||||||
|
merchant_id: &str,
|
||||||
|
refund_details: &api_models::refunds::RefundListRequest,
|
||||||
|
storage_scheme: enums::MerchantStorageScheme,
|
||||||
|
limit: i64,
|
||||||
|
) -> CustomResult<Vec<storage_models::refund::Refund>, errors::StorageError> {
|
||||||
|
match storage_scheme {
|
||||||
|
enums::MerchantStorageScheme::PostgresOnly => {
|
||||||
|
let conn = pg_connection(&self.master_pool).await;
|
||||||
|
<storage_models::refund::Refund as storage_types::RefundDbExt>::filter_by_constraints(&conn, merchant_id, refund_details, limit)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
.into_report()
|
||||||
|
}
|
||||||
|
|
||||||
|
enums::MerchantStorageScheme::RedisKv => Err(errors::StorageError::KVError.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,4 +698,15 @@ impl RefundInterface for MockDb {
|
|||||||
// [#172]: Implement function for `MockDb`
|
// [#172]: Implement function for `MockDb`
|
||||||
Err(errors::StorageError::MockDbError)?
|
Err(errors::StorageError::MockDbError)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn filter_refund_by_constraints(
|
||||||
|
&self,
|
||||||
|
_merchant_id: &str,
|
||||||
|
_refund_details: &api_models::refunds::RefundListRequest,
|
||||||
|
_storage_scheme: enums::MerchantStorageScheme,
|
||||||
|
_limit: i64,
|
||||||
|
) -> CustomResult<Vec<storage_models::refund::Refund>, errors::StorageError> {
|
||||||
|
// [#172]: Implement function for `MockDb`
|
||||||
|
Err(errors::StorageError::MockDbError)?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -113,12 +113,12 @@ impl Refunds {
|
|||||||
web::scope("/refunds")
|
web::scope("/refunds")
|
||||||
.app_data(web::Data::new(state))
|
.app_data(web::Data::new(state))
|
||||||
.service(web::resource("").route(web::post().to(refunds_create)))
|
.service(web::resource("").route(web::post().to(refunds_create)))
|
||||||
|
.service(web::resource("/list").route(web::get().to(refunds_list)))
|
||||||
.service(
|
.service(
|
||||||
web::resource("/{id}")
|
web::resource("/{id}")
|
||||||
.route(web::get().to(refunds_retrieve))
|
.route(web::get().to(refunds_retrieve))
|
||||||
.route(web::post().to(refunds_update)),
|
.route(web::post().to(refunds_update)),
|
||||||
)
|
)
|
||||||
.service(web::resource("/list").route(web::get().to(refunds_list)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -68,6 +68,17 @@ pub async fn refunds_update(
|
|||||||
|
|
||||||
#[instrument(skip_all, fields(flow = ?Flow::RefundsList))]
|
#[instrument(skip_all, fields(flow = ?Flow::RefundsList))]
|
||||||
// #[get("/list")]
|
// #[get("/list")]
|
||||||
pub async fn refunds_list() -> HttpResponse {
|
pub async fn refunds_list(
|
||||||
api::http_response_json("list")
|
state: web::Data<AppState>,
|
||||||
|
req: HttpRequest,
|
||||||
|
payload: web::Query<api_models::refunds::RefundListRequest>,
|
||||||
|
) -> HttpResponse {
|
||||||
|
api::server_wrap(
|
||||||
|
&state,
|
||||||
|
&req,
|
||||||
|
payload.into_inner(),
|
||||||
|
|state, merchant_account, req| refund_list(&*state.store, merchant_account, req),
|
||||||
|
api::MerchantAuthentication::ApiKey,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,72 @@
|
|||||||
|
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::refund::{
|
pub use storage_models::refund::{
|
||||||
Refund, RefundCoreWorkflow, RefundNew, RefundUpdate, RefundUpdateInternal,
|
Refund, RefundCoreWorkflow, RefundNew, RefundUpdate, RefundUpdateInternal,
|
||||||
};
|
};
|
||||||
|
use storage_models::{errors, schema::refund::dsl};
|
||||||
|
|
||||||
|
use crate::{connection::PgPooledConn, logger};
|
||||||
|
|
||||||
#[cfg(feature = "kv_store")]
|
#[cfg(feature = "kv_store")]
|
||||||
impl crate::utils::storage_partitioning::KvStorePartition for Refund {}
|
impl crate::utils::storage_partitioning::KvStorePartition for Refund {}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait RefundDbExt: Sized {
|
||||||
|
async fn filter_by_constraints(
|
||||||
|
conn: &PgPooledConn,
|
||||||
|
merchant_id: &str,
|
||||||
|
refund_list_details: &api_models::refunds::RefundListRequest,
|
||||||
|
limit: i64,
|
||||||
|
) -> CustomResult<Vec<Self>, errors::DatabaseError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl RefundDbExt for Refund {
|
||||||
|
async fn filter_by_constraints(
|
||||||
|
conn: &PgPooledConn,
|
||||||
|
merchant_id: &str,
|
||||||
|
refund_list_details: &api_models::refunds::RefundListRequest,
|
||||||
|
limit: i64,
|
||||||
|
) -> CustomResult<Vec<Self>, errors::DatabaseError> {
|
||||||
|
let mut filter = <Self as HasTable>::table()
|
||||||
|
.filter(dsl::merchant_id.eq(merchant_id.to_owned()))
|
||||||
|
.order_by(dsl::id)
|
||||||
|
.into_boxed();
|
||||||
|
|
||||||
|
match &refund_list_details.payment_id {
|
||||||
|
Some(pid) => {
|
||||||
|
filter = filter.filter(dsl::payment_id.eq(pid.to_owned()));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
filter = filter.limit(limit);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(created) = refund_list_details.created {
|
||||||
|
filter = filter.filter(dsl::created_at.eq(created));
|
||||||
|
}
|
||||||
|
if let Some(created_lt) = refund_list_details.created_lt {
|
||||||
|
filter = filter.filter(dsl::created_at.lt(created_lt));
|
||||||
|
}
|
||||||
|
if let Some(created_gt) = refund_list_details.created_gt {
|
||||||
|
filter = filter.filter(dsl::created_at.gt(created_gt));
|
||||||
|
}
|
||||||
|
if let Some(created_lte) = refund_list_details.created_lte {
|
||||||
|
filter = filter.filter(dsl::created_at.le(created_lte));
|
||||||
|
}
|
||||||
|
if let Some(created_gte) = refund_list_details.created_gte {
|
||||||
|
filter = filter.filter(dsl::created_at.gt(created_gte));
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user