phacility/phabricator · error · Exception
Trying to refund more money than remaining on charge!
Error message
Trying to refund more money than remaining on charge!
What it means
willRefundCharge() throws when the requested amount exceeds $charge->getAmountRefundableAsCurrency(), the original charge amount minus all completed refunds. This prevents refunding more money than was ever captured on that charge. It is the main over-refund guard for partial refunds.
Source
Thrown at src/applications/phortune/storage/PhortuneCart.php:320
$this->saveTransaction();
return $this;
}
public function willRefundCharge(
PhabricatorUser $actor,
PhortunePaymentProvider $provider,
PhortuneCharge $charge,
PhortuneCurrency $amount) {
if (!$amount->isPositive()) {
throw new Exception(
pht('Trying to refund non-positive amount of money!'));
}
if ($amount->isGreaterThan($charge->getAmountRefundableAsCurrency())) {
throw new Exception(
pht('Trying to refund more money than remaining on charge!'));
}
if ($charge->getRefundedChargePHID()) {
throw new Exception(
pht('Trying to refund a refund!'));
}
if (($charge->getStatus() !== PhortuneCharge::STATUS_CHARGED) &&
($charge->getStatus() !== PhortuneCharge::STATUS_HOLD)) {
throw new Exception(
pht('Trying to refund an uncharged charge!'));
}
$refund_charge = PhortuneCharge::initializeNewCharge()
->setAccountPHID($this->getAccount()->getPHID())
->setCartPHID($this->getPHID())
->setAuthorPHID($actor->getPHID())View on GitHub (pinned to 5720a38cfe)
Solutions
- Cap the requested amount at $charge->getAmountRefundableAsCurrency() and prefill refund forms with that value.
- Reload the charge immediately before refunding so prior refunds are reflected in the refundable total.
- Serialize refunds per charge (single refund button, lockstep flow) so the refundable balance cannot be double-spent.
Example fix
// before
$amount = $charge->getAmountAsCurrency(); // ignores earlier partial refund
$cart->willRefundCharge($actor, $provider, $charge, $amount); // throws: exceeds refundable
// after
$amount = $charge->getAmountRefundableAsCurrency();
if ($requested->isGreaterThan($amount)) {
$requested = $amount; // clamp to remaining refundable
}
if ($requested->isPositive()) {
$cart->willRefundCharge($actor, $provider, $charge, $requested);
} Defensive patterns
Strategy: validation
Validate before calling
$refundable = $charge->getAmountRefundableAsCurrency();
if (!$amount->isPositive()) {
$errors[] = pht('Refund amount must be greater than zero.');
} else if ($amount->isGreaterThan($refundable)) {
$amount = $refundable; // or reject with an error
} Prevention
- Always compute refund ceilings from getAmountRefundableAsCurrency(), never the original amount.
- Reload the charge before refunding so prior refunds are counted.
- Serialize refunds per charge so the refundable balance cannot be double-spent.
When it happens
Trigger: Refunding the full charge amount again after an earlier partial or full refund; computing the refund from getAmountAsCurrency() instead of getAmountRefundableAsCurrency(); two concurrent refund flows each reading the same refundable balance.
Common situations: Support staff issuing repeated partial refunds; refund forms prefilled with the original price rather than remaining refundable; race between two admins refunding the same charge.
Related errors
- Charge has no transaction ID!
- Unable to refund charge; no Stripe chargeID!
- Trying to refund non-positive amount of money!
- Trying to refund a refund!
- Trying to refund an uncharged charge!
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/c4d9d7a6a7862e3a.
Report an issue: GitHub.