#14264·hyperswitch

[BUG] [Refunds]: refund.max_attempts is off by one; default of 10 permits 11 refunds

Author: nfarah86Created Sep 16, 2026Updated Sep 17, 2026
LabelsC-bugS-awaiting-triage

Bug Description

refund.max_attempts is enforced with a strict > against the count of refunds that already exist, and the check runs before the new refund is inserted. The effect is an off-by-one: with the default max_attempts: 10, a payment attempt can end up with 11 refunds.

The check, in refunds_validator.rs#L93-L100:

rust
pub fn validate_maximum_refund_against_payment_attempt(
    all_refunds: &[diesel_refund::Refund],
    refund_max_attempts: usize,
) -> CustomResult<(), RefundValidationError> {
    utils::when(all_refunds.len() > refund_max_attempts, || {
        Err(report!(RefundValidationError::MaxRefundCountReached))
    })
}

In validate_and_create_refund (refunds.rs), all_refunds is fetched at L1644 for the payment attempt's connector_transaction_id, the count check runs at L1676, and the new refund is built and inserted afterwards at L1705 and L1743. So when 10 refunds already exist, 10 > 10 is false, the check passes, and an 11th refund is created. The 12th is rejected.

The default is max_attempts: 10 (defaults.rs#L92-L97).

Expected Behavior

max_attempts: 10 allows at most 10 refunds per payment attempt. The comparison should be all_refunds.len() >= refund_max_attempts, since the check runs before the insert.

Actual Behavior

11 refunds can be created on one payment attempt with the default setting: the configured value, plus one.

Steps To Reproduce

  1. Create and capture a payment with an amount large enough to split into many partial refunds (for example 1200 in the minor unit).
  2. Create refunds of 100 each against that payment, one at a time.
  3. The 11th refund is accepted.
  4. The 12th fails with MaximumRefundCount.

Note the amount check (validate_refund_amount, L1671) usually binds first, so the captured amount must be large enough to leave room for 11 refunds. Failed refunds also count toward the attempt total even though they do not consume the refundable amount.

Context For The Bug

Found while documenting refund semantics for the docs (juspay/hyperswitch-docs#270). The docs page currently has to describe the actual behavior ("the default of 10 allows up to 11 refunds"), which reads as a bug rather than an intended limit.

This is code inspection, not an executed test.

Environment

Code inspection at main (184ffd4c015fd3fea2f3868549f1a86ffa5f40da); also present at 02ba5ea0b4cc0ad8debea4cefd28a7de5916d9db.

Have you spent some time checking if this bug has been raised before?

  • I checked and didn't find a similar issue