Skip to content

feat(core): Add support for Void after Capture #8839

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Aug 6, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions api-reference/v1/openapi_spec_v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,67 @@
]
}
},
"/payments/{payment_id}/cancel_post_capture": {
"post": {
"tags": [
"Payments"
],
"summary": "Payments - Cancel Post Capture",
"description": "A Payment could can be cancelled when it is in one of these statuses: `requires_payment_method`, `requires_capture`, `requires_confirmation`, `requires_customer_action`.",
"operationId": "Cancel a Payment Post Capture",
"parameters": [
{
"name": "payment_id",
"in": "path",
"description": "The identifier for payment",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PaymentsCancelPostCaptureRequest"
},
"examples": {
"Cancel the payment post capture with cancellation reason": {
"value": {
"cancellation_reason": "requested_by_customer"
}
},
"Cancel the payment post capture with minimal fields": {
"value": {}
}
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Payment canceled"
},
"400": {
"description": "Missing mandatory fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenericErrorResponseOpenApi"
}
}
}
}
},
"security": [
{
"api_key": []
}
]
}
},
"/payments/list": {
"get": {
"tags": [
Expand Down Expand Up @@ -7826,6 +7887,7 @@
"authorizing",
"cod_initiated",
"voided",
"voided_post_charge",
"void_initiated",
"capture_initiated",
"capture_failed",
Expand Down Expand Up @@ -14639,6 +14701,7 @@
"payment_failed",
"payment_processing",
"payment_cancelled",
"payment_cancelled_post_capture",
"payment_authorized",
"payment_captured",
"payment_expired",
Expand Down Expand Up @@ -16486,6 +16549,7 @@
"succeeded",
"failed",
"cancelled",
"cancelled_post_capture",
"processing",
"requires_customer_action",
"requires_merchant_action",
Expand Down Expand Up @@ -21937,6 +22001,16 @@
"recurring_mandate"
]
},
"PaymentsCancelPostCaptureRequest": {
"type": "object",
"properties": {
"cancellation_reason": {
"type": "string",
"description": "The reason for the payment cancel",
"nullable": true
}
}
},
"PaymentsCancelRequest": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -31421,6 +31495,7 @@
"AUTHORIZING",
"C_O_D_INITIATED",
"VOIDED",
"VOIDED_POST_CHARGE",
"VOID_INITIATED",
"NOP",
"CAPTURE_INITIATED",
Expand Down
13 changes: 13 additions & 0 deletions api-reference/v2/openapi_spec_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -4834,6 +4834,7 @@
"authorizing",
"cod_initiated",
"voided",
"voided_post_charge",
"void_initiated",
"capture_initiated",
"capture_failed",
Expand Down Expand Up @@ -10686,6 +10687,7 @@
"payment_failed",
"payment_processing",
"payment_cancelled",
"payment_cancelled_post_capture",
"payment_authorized",
"payment_captured",
"payment_expired",
Expand Down Expand Up @@ -12645,6 +12647,7 @@
"succeeded",
"failed",
"cancelled",
"cancelled_post_capture",
"processing",
"requires_customer_action",
"requires_merchant_action",
Expand Down Expand Up @@ -18264,6 +18267,16 @@
"recurring_mandate"
]
},
"PaymentsCancelPostCaptureRequest": {
"type": "object",
"properties": {
"cancellation_reason": {
"type": "string",
"description": "The reason for the payment cancel",
"nullable": true
}
}
},
"PaymentsCancelRequest": {
"type": "object",
"properties": {
Expand Down
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 @@ -12,8 +12,8 @@ use crate::{
payment_methods::PaymentMethodListResponse,
payments::{
ExtendedCardInfoResponse, PaymentIdType, PaymentListFilterConstraints,
PaymentListResponseV2, PaymentsApproveRequest, PaymentsCancelRequest,
PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest,
PaymentListResponseV2, PaymentsApproveRequest, PaymentsCancelPostCaptureRequest,
PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest,
PaymentsDynamicTaxCalculationRequest, PaymentsDynamicTaxCalculationResponse,
PaymentsExternalAuthenticationRequest, PaymentsExternalAuthenticationResponse,
PaymentsIncrementalAuthorizationRequest, PaymentsManualUpdateRequest,
Expand Down Expand Up @@ -132,6 +132,15 @@ impl ApiEventMetric for PaymentsCancelRequest {
}
}

#[cfg(feature = "v1")]
impl ApiEventMetric for PaymentsCancelPostCaptureRequest {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Some(ApiEventsType::Payment {
payment_id: self.payment_id.clone(),
})
}
}

#[cfg(feature = "v1")]
impl ApiEventMetric for PaymentsApproveRequest {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Expand Down
1 change: 1 addition & 0 deletions crates/api_models/src/open_router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ pub enum TxnStatus {
Authorizing,
CODInitiated,
Voided,
VoidedPostCharge,
VoidInitiated,
Nop,
CaptureInitiated,
Expand Down
10 changes: 10 additions & 0 deletions crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7637,6 +7637,16 @@ pub struct PaymentsCancelRequest {
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
}

/// Request to cancel a payment when the payment is already captured
#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
pub struct PaymentsCancelPostCaptureRequest {
/// The identifier for the payment
#[serde(skip)]
pub payment_id: id_type::PaymentId,
/// The reason for the payment cancel
pub cancellation_reason: Option<String>,
}

#[derive(Default, Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)]
pub struct PaymentsIncrementalAuthorizationRequest {
/// The identifier for the payment
Expand Down
8 changes: 8 additions & 0 deletions crates/common_enums/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ pub enum AttemptStatus {
Authorizing,
CodInitiated,
Voided,
VoidedPostCharge,
VoidInitiated,
CaptureInitiated,
CaptureFailed,
Expand All @@ -166,6 +167,7 @@ impl AttemptStatus {
| Self::Charged
| Self::AutoRefunded
| Self::Voided
| Self::VoidedPostCharge
| Self::VoidFailed
| Self::CaptureFailed
| Self::Failure
Expand Down Expand Up @@ -1555,6 +1557,7 @@ pub enum EventType {
PaymentFailed,
PaymentProcessing,
PaymentCancelled,
PaymentCancelledPostCapture,
PaymentAuthorized,
PaymentCaptured,
PaymentExpired,
Expand Down Expand Up @@ -1659,6 +1662,8 @@ pub enum IntentStatus {
Failed,
/// This payment has been cancelled.
Cancelled,
/// This payment has been cancelled post capture.
CancelledPostCapture,
/// This payment is still being processed by the payment processor.
/// The status update might happen through webhooks or polling with the connector.
Processing,
Expand Down Expand Up @@ -1690,6 +1695,7 @@ impl IntentStatus {
Self::Succeeded
| Self::Failed
| Self::Cancelled
| Self::CancelledPostCapture
| Self::PartiallyCaptured
| Self::Expired => true,
Self::Processing
Expand All @@ -1713,6 +1719,7 @@ impl IntentStatus {
| Self::Succeeded
| Self::Failed
| Self::Cancelled
| Self::CancelledPostCapture
| Self::PartiallyCaptured
| Self::RequiresCapture | Self::Conflicted | Self::Expired=> false,
Self::Processing
Expand Down Expand Up @@ -1826,6 +1833,7 @@ impl From<AttemptStatus> for PaymentMethodStatus {
match attempt_status {
AttemptStatus::Failure
| AttemptStatus::Voided
| AttemptStatus::VoidedPostCharge
| AttemptStatus::Started
| AttemptStatus::Pending
| AttemptStatus::Unresolved
Expand Down
2 changes: 2 additions & 0 deletions crates/common_enums/src/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2123,6 +2123,7 @@ impl From<AttemptStatus> for IntentStatus {
| AttemptStatus::CaptureFailed
| AttemptStatus::Failure => Self::Failed,
AttemptStatus::Voided => Self::Cancelled,
AttemptStatus::VoidedPostCharge => Self::CancelledPostCapture,
AttemptStatus::Expired => Self::Expired,
}
}
Expand All @@ -2138,6 +2139,7 @@ impl From<IntentStatus> for Option<EventType> {
| IntentStatus::RequiresCustomerAction
| IntentStatus::Conflicted => Some(EventType::ActionRequired),
IntentStatus::Cancelled => Some(EventType::PaymentCancelled),
IntentStatus::CancelledPostCapture => Some(EventType::PaymentCancelledPostCapture),
IntentStatus::Expired => Some(EventType::PaymentExpired),
IntentStatus::PartiallyCaptured | IntentStatus::PartiallyCapturedAndCapturable => {
Some(EventType::PaymentCaptured)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,7 @@ impl TryFrom<enums::AttemptStatus> for ChargebeeRecordStatus {
| enums::AttemptStatus::Authorizing
| enums::AttemptStatus::CodInitiated
| enums::AttemptStatus::Voided
| enums::AttemptStatus::VoidedPostCharge
| enums::AttemptStatus::VoidInitiated
| enums::AttemptStatus::CaptureInitiated
| enums::AttemptStatus::VoidFailed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2706,6 +2706,7 @@ impl TryFrom<PaymentsCaptureResponseRouterData<PaypalCaptureResponse>>
| storage_enums::AttemptStatus::ConfirmationAwaited
| storage_enums::AttemptStatus::DeviceDataCollectionPending
| storage_enums::AttemptStatus::Voided
| storage_enums::AttemptStatus::VoidedPostCharge
| storage_enums::AttemptStatus::Expired => 0,
storage_enums::AttemptStatus::Charged
| storage_enums::AttemptStatus::PartialCharged
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ impl TryFrom<enums::AttemptStatus> for RecurlyRecordStatus {
| enums::AttemptStatus::Authorizing
| enums::AttemptStatus::CodInitiated
| enums::AttemptStatus::Voided
| enums::AttemptStatus::VoidedPostCharge
| enums::AttemptStatus::VoidInitiated
| enums::AttemptStatus::CaptureInitiated
| enums::AttemptStatus::VoidFailed
Expand Down
Loading
Loading