phacility/phabricator · error · Exception

Unable to refund some charges!

Error message

Unable to refund some charges!

What it means

After iterating all refundable charges (each attempt can fail inside the provider, leaving $refunded false for that charge), any remaining positive refund amount triggers 'Unable to refund some charges!'. It is an aggregate partial-failure signal: the requested refund could not be covered by the charges available, because at least one refund was rejected or had insufficient refundable balance.

Source

Thrown at src/applications/phortune/controller/cart/PhortuneCartCancelController.php:152

            $provider->refundCharge($charge, $refund_charge);
            $refunded = true;
          } catch (Exception $ex) {
            phlog($ex);
            $cart->didFailRefund($charge, $refund_charge);
          }

          if ($refunded) {
            $cart->didRefundCharge($charge, $refund_charge);
            $refund = $refund->subtract($refund_amount);
          }

          if (!$refund->isPositive()) {
            break;
          }
        }

        if ($refund->isPositive()) {
          throw new Exception(pht('Unable to refund some charges!'));
        }

        // TODO: If every HOLD and CHARGING transaction has been fully refunded
        // and we're in a HOLD, REVIEW, PURCHASING or CHARGED cart state we
        // probably need to kick the cart back to READY here (or maybe kill
        // it if it was in REVIEW)?

        return id(new AphrontRedirectResponse())->setURI($cancel_uri);
      }
    }

    if ($is_refund) {
      $title = pht('Refund Order?');
      $body = pht('Really refund this order?');
      $button = pht('Refund Order');
      $cancel_text = pht('Cancel');

      $form = id(new AphrontFormView())

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Reconcile each charge's refund state in Phortune against the gateway's transaction history
  2. Fix the underlying provider failure (credentials, connectivity) and retry the cancel/refund
  3. Complete any remainder as a gateway-side refund and record it manually so balances match
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check that total refundable balance covers the requested refund
$refundable_total = 0;
foreach ($charges as $charge) {
  if (!$charge->isRefund()) {
    $refundable_total += $charge->getAmountRefundable()->getValue();
  }
}
if ($refundable_total < $refund->getValue()) {
  throw new Exception('Insufficient refundable balance to cover the refund.');
}

Try / catch

try {
  // cancel + refund flow
} catch (Exception $ex) {
  if (strpos($ex->getMessage(), 'Unable to refund some charges') !== false) {
    // re-render the cart/charge table so operators see which charges remain unrefunded
  }
}

Prevention

When it happens

Trigger: A provider API rejects one refund (insufficient funds at the gateway, auth failure, outage); part of the money was already refunded externally in the gateway dashboard; requesting a refund larger than what the un-refunded charge balances cover.

Common situations: Partial manual refunds done outside Phortune desynchronizing balances; transient gateway outages mid-cancel; charge-hold corner cases during double-clicks.

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/896837b3dfc5bdc8. Report an issue: GitHub.