mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +08:00
feat(refunds_v2): Add refund update core flow in v2 apis (#7724)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -3265,6 +3265,64 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v2/refunds/{id}/update_metadata": {
|
||||||
|
"put": {
|
||||||
|
"tags": [
|
||||||
|
"Refunds"
|
||||||
|
],
|
||||||
|
"summary": "Refunds - Metadata Update",
|
||||||
|
"description": "Updates the properties of a Refund object. This API can be used to attach a reason for the refund or metadata fields",
|
||||||
|
"operationId": "Update Refund Metadata and Reason",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "The identifier for refund",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/RefundMetadataUpdateRequest"
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"Update refund reason": {
|
||||||
|
"value": {
|
||||||
|
"reason": "Paid by mistake"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Refund updated",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/RefundResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Missing Mandatory fields"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"api_key": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v2/refunds/{id}": {
|
"/v2/refunds/{id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@ -20629,6 +20687,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"RefundMetadataUpdateRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"reason": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "An arbitrary string attached to the object. Often useful for displaying to users and your customer support executive",
|
||||||
|
"example": "Customer returned the product",
|
||||||
|
"nullable": true,
|
||||||
|
"maxLength": 255
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.",
|
||||||
|
"nullable": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"RefundResponse": {
|
"RefundResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@ -20737,24 +20813,6 @@
|
|||||||
"instant"
|
"instant"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"RefundUpdateRequest": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"reason": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "An arbitrary string attached to the object. Often useful for displaying to users and your customer support executive",
|
|
||||||
"example": "Customer returned the product",
|
|
||||||
"nullable": true,
|
|
||||||
"maxLength": 255
|
|
||||||
},
|
|
||||||
"metadata": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.",
|
|
||||||
"nullable": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
"RefundsCreateRequest": {
|
"RefundsCreateRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|||||||
@ -166,6 +166,19 @@ pub struct RefundUpdateRequest {
|
|||||||
pub metadata: Option<pii::SecretSerdeValue>,
|
pub metadata: Option<pii::SecretSerdeValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
|
||||||
|
#[derive(Default, Debug, ToSchema, Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct RefundMetadataUpdateRequest {
|
||||||
|
/// An arbitrary string attached to the object. Often useful for displaying to users and your customer support executive
|
||||||
|
#[schema(max_length = 255, example = "Customer returned the product")]
|
||||||
|
pub reason: Option<String>,
|
||||||
|
|
||||||
|
/// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.
|
||||||
|
#[schema(value_type = Option<Object>, example = r#"{ "city": "NY", "unit": "245" }"#)]
|
||||||
|
pub metadata: Option<pii::SecretSerdeValue>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, ToSchema, Clone, Deserialize, Serialize)]
|
#[derive(Default, Debug, ToSchema, Clone, Deserialize, Serialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct RefundManualUpdateRequest {
|
pub struct RefundManualUpdateRequest {
|
||||||
|
|||||||
@ -148,6 +148,7 @@ Never share your secret api keys. Keep them guarded and secure.
|
|||||||
|
|
||||||
//Routes for refunds
|
//Routes for refunds
|
||||||
routes::refunds::refunds_create,
|
routes::refunds::refunds_create,
|
||||||
|
routes::refunds::refunds_metadata_update,
|
||||||
routes::refunds::refunds_retrieve,
|
routes::refunds::refunds_retrieve,
|
||||||
routes::refunds::refunds_list,
|
routes::refunds::refunds_list,
|
||||||
|
|
||||||
@ -193,7 +194,7 @@ Never share your secret api keys. Keep them guarded and secure.
|
|||||||
api_models::refunds::RefundType,
|
api_models::refunds::RefundType,
|
||||||
api_models::refunds::RefundResponse,
|
api_models::refunds::RefundResponse,
|
||||||
api_models::refunds::RefundStatus,
|
api_models::refunds::RefundStatus,
|
||||||
api_models::refunds::RefundUpdateRequest,
|
api_models::refunds::RefundMetadataUpdateRequest,
|
||||||
api_models::organization::OrganizationCreateRequest,
|
api_models::organization::OrganizationCreateRequest,
|
||||||
api_models::organization::OrganizationUpdateRequest,
|
api_models::organization::OrganizationUpdateRequest,
|
||||||
api_models::organization::OrganizationResponse,
|
api_models::organization::OrganizationResponse,
|
||||||
|
|||||||
@ -112,6 +112,7 @@ pub async fn refunds_retrieve_with_body() {}
|
|||||||
operation_id = "Update a Refund",
|
operation_id = "Update a Refund",
|
||||||
security(("api_key" = []))
|
security(("api_key" = []))
|
||||||
)]
|
)]
|
||||||
|
#[cfg(feature = "v1")]
|
||||||
pub async fn refunds_update() {}
|
pub async fn refunds_update() {}
|
||||||
|
|
||||||
/// Refunds - List
|
/// Refunds - List
|
||||||
@ -215,6 +216,38 @@ pub async fn refunds_filter_list() {}
|
|||||||
#[cfg(feature = "v2")]
|
#[cfg(feature = "v2")]
|
||||||
pub async fn refunds_create() {}
|
pub async fn refunds_create() {}
|
||||||
|
|
||||||
|
/// Refunds - Metadata Update
|
||||||
|
///
|
||||||
|
/// Updates the properties of a Refund object. This API can be used to attach a reason for the refund or metadata fields
|
||||||
|
#[utoipa::path(
|
||||||
|
put,
|
||||||
|
path = "/v2/refunds/{id}/update_metadata",
|
||||||
|
params(
|
||||||
|
("id" = String, Path, description = "The identifier for refund")
|
||||||
|
),
|
||||||
|
request_body(
|
||||||
|
content = RefundMetadataUpdateRequest,
|
||||||
|
examples(
|
||||||
|
(
|
||||||
|
"Update refund reason" = (
|
||||||
|
value = json!({
|
||||||
|
"reason": "Paid by mistake"
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Refund updated", body = RefundResponse),
|
||||||
|
(status = 400, description = "Missing Mandatory fields")
|
||||||
|
),
|
||||||
|
tag = "Refunds",
|
||||||
|
operation_id = "Update Refund Metadata and Reason",
|
||||||
|
security(("api_key" = []))
|
||||||
|
)]
|
||||||
|
#[cfg(feature = "v2")]
|
||||||
|
pub async fn refunds_metadata_update() {}
|
||||||
|
|
||||||
/// Refunds - Retrieve
|
/// Refunds - Retrieve
|
||||||
///
|
///
|
||||||
/// Retrieves a Refund. This may be used to get the status of a previously initiated refund
|
/// Retrieves a Refund. This may be used to get the status of a previously initiated refund
|
||||||
|
|||||||
@ -465,6 +465,42 @@ where
|
|||||||
request.check_integrity(request, connector_refund_id.to_owned())
|
request.check_integrity(request, connector_refund_id.to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ********************************************** REFUND UPDATE **********************************************
|
||||||
|
|
||||||
|
pub async fn refund_metadata_update_core(
|
||||||
|
state: SessionState,
|
||||||
|
merchant_account: domain::MerchantAccount,
|
||||||
|
req: refunds::RefundMetadataUpdateRequest,
|
||||||
|
global_refund_id: id_type::GlobalRefundId,
|
||||||
|
) -> errors::RouterResponse<refunds::RefundResponse> {
|
||||||
|
let db = state.store.as_ref();
|
||||||
|
let refund = db
|
||||||
|
.find_refund_by_id(&global_refund_id, merchant_account.storage_scheme)
|
||||||
|
.await
|
||||||
|
.to_not_found_response(errors::ApiErrorResponse::RefundNotFound)?;
|
||||||
|
|
||||||
|
let response = db
|
||||||
|
.update_refund(
|
||||||
|
refund,
|
||||||
|
storage::RefundUpdate::MetadataAndReasonUpdate {
|
||||||
|
metadata: req.metadata,
|
||||||
|
reason: req.reason,
|
||||||
|
updated_by: merchant_account.storage_scheme.to_string(),
|
||||||
|
},
|
||||||
|
merchant_account.storage_scheme,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
|
.attach_printable_lazy(|| {
|
||||||
|
format!(
|
||||||
|
"Unable to update refund with refund_id: {}",
|
||||||
|
global_refund_id.get_string_repr()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
refunds::RefundResponse::foreign_try_from(response).map(services::ApplicationResponse::Json)
|
||||||
|
}
|
||||||
|
|
||||||
// ********************************************** REFUND SYNC **********************************************
|
// ********************************************** REFUND SYNC **********************************************
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
|
|||||||
@ -1187,7 +1187,11 @@ impl Refunds {
|
|||||||
{
|
{
|
||||||
route = route
|
route = route
|
||||||
.service(web::resource("").route(web::post().to(refunds::refunds_create)))
|
.service(web::resource("").route(web::post().to(refunds::refunds_create)))
|
||||||
.service(web::resource("/{id}").route(web::get().to(refunds::refunds_retrieve)));
|
.service(web::resource("/{id}").route(web::get().to(refunds::refunds_retrieve)))
|
||||||
|
.service(
|
||||||
|
web::resource("/{id}/update_metadata")
|
||||||
|
.route(web::put().to(refunds::refunds_metadata_update)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
route
|
route
|
||||||
|
|||||||
@ -12,6 +12,39 @@ use crate::{
|
|||||||
services::{api, authentication as auth, authorization::permissions::Permission},
|
services::{api, authentication as auth, authorization::permissions::Permission},
|
||||||
types::{api::refunds, domain},
|
types::{api::refunds, domain},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "v2")]
|
||||||
|
/// A private module to hold internal types to be used in route handlers.
|
||||||
|
/// This is because we will need to implement certain traits on these types which will have the resource id
|
||||||
|
/// But the api payload will not contain the resource id
|
||||||
|
/// So these types can hold the resource id along with actual api payload, on which api event and locking action traits can be implemented
|
||||||
|
mod internal_payload_types {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// Serialize is implemented because of api events
|
||||||
|
#[derive(Debug, serde::Serialize)]
|
||||||
|
pub struct RefundsGenericRequestWithResourceId<T: serde::Serialize> {
|
||||||
|
pub global_refund_id: common_utils::id_type::GlobalRefundId,
|
||||||
|
pub payment_id: Option<common_utils::id_type::GlobalPaymentId>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub payload: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: serde::Serialize> common_utils::events::ApiEventMetric
|
||||||
|
for RefundsGenericRequestWithResourceId<T>
|
||||||
|
{
|
||||||
|
fn get_api_event_type(&self) -> Option<common_utils::events::ApiEventsType> {
|
||||||
|
let refund_id = self.global_refund_id.clone();
|
||||||
|
self.payment_id
|
||||||
|
.clone()
|
||||||
|
.map(|payment_id| common_utils::events::ApiEventsType::Refund {
|
||||||
|
payment_id,
|
||||||
|
refund_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Refunds - Create
|
/// Refunds - Create
|
||||||
///
|
///
|
||||||
/// To create a refund against an already processed payment
|
/// To create a refund against an already processed payment
|
||||||
@ -323,6 +356,45 @@ pub async fn refunds_update(
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
|
||||||
|
#[instrument(skip_all, fields(flow = ?Flow::RefundsUpdate))]
|
||||||
|
pub async fn refunds_metadata_update(
|
||||||
|
state: web::Data<AppState>,
|
||||||
|
req: HttpRequest,
|
||||||
|
json_payload: web::Json<refunds::RefundMetadataUpdateRequest>,
|
||||||
|
path: web::Path<common_utils::id_type::GlobalRefundId>,
|
||||||
|
) -> HttpResponse {
|
||||||
|
let flow = Flow::RefundsUpdate;
|
||||||
|
|
||||||
|
let global_refund_id = path.into_inner();
|
||||||
|
let internal_payload = internal_payload_types::RefundsGenericRequestWithResourceId {
|
||||||
|
global_refund_id: global_refund_id.clone(),
|
||||||
|
payment_id: None,
|
||||||
|
payload: json_payload.into_inner(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Box::pin(api::server_wrap(
|
||||||
|
flow,
|
||||||
|
state,
|
||||||
|
&req,
|
||||||
|
internal_payload,
|
||||||
|
|state, auth: auth::AuthenticationData, req, _| {
|
||||||
|
refund_metadata_update_core(
|
||||||
|
state,
|
||||||
|
auth.merchant_account,
|
||||||
|
req.payload,
|
||||||
|
global_refund_id.clone(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
&auth::V2ApiKeyAuth {
|
||||||
|
is_connected_allowed: false,
|
||||||
|
is_platform_allowed: false,
|
||||||
|
},
|
||||||
|
api_locking::LockAction::NotApplicable,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
any(feature = "v1", feature = "v2"),
|
any(feature = "v1", feature = "v2"),
|
||||||
not(feature = "refunds_v2"),
|
not(feature = "refunds_v2"),
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "refunds_v2")))]
|
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "refunds_v2")))]
|
||||||
pub use api_models::refunds::RefundRequest;
|
pub use api_models::refunds::RefundRequest;
|
||||||
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
|
|
||||||
pub use api_models::refunds::RefundsCreateRequest;
|
|
||||||
pub use api_models::refunds::{
|
pub use api_models::refunds::{
|
||||||
RefundListRequest, RefundListResponse, RefundResponse, RefundStatus, RefundType,
|
RefundListRequest, RefundListResponse, RefundResponse, RefundStatus, RefundType,
|
||||||
RefundUpdateRequest, RefundsRetrieveBody, RefundsRetrieveRequest,
|
RefundUpdateRequest, RefundsRetrieveBody, RefundsRetrieveRequest,
|
||||||
};
|
};
|
||||||
|
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
|
||||||
|
pub use api_models::refunds::{RefundMetadataUpdateRequest, RefundsCreateRequest};
|
||||||
pub use hyperswitch_domain_models::router_flow_types::refunds::{Execute, RSync};
|
pub use hyperswitch_domain_models::router_flow_types::refunds::{Execute, RSync};
|
||||||
pub use hyperswitch_interfaces::api::refunds::{Refund, RefundExecute, RefundSync};
|
pub use hyperswitch_interfaces::api::refunds::{Refund, RefundExecute, RefundSync};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user