{"record":{"id":"896837b3dfc5bdc8","repo":"phacility/phabricator","slug":"unable-to-refund-some-charges","errorCode":null,"errorMessage":"Unable to refund some charges!","messagePattern":"Unable to refund some charges!","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/phortune/controller/cart/PhortuneCartCancelController.php","lineNumber":152,"sourceCode":"            $provider->refundCharge($charge, $refund_charge);\n            $refunded = true;\n          } catch (Exception $ex) {\n            phlog($ex);\n            $cart->didFailRefund($charge, $refund_charge);\n          }\n\n          if ($refunded) {\n            $cart->didRefundCharge($charge, $refund_charge);\n            $refund = $refund->subtract($refund_amount);\n          }\n\n          if (!$refund->isPositive()) {\n            break;\n          }\n        }\n\n        if ($refund->isPositive()) {\n          throw new Exception(pht('Unable to refund some charges!'));\n        }\n\n        // TODO: If every HOLD and CHARGING transaction has been fully refunded\n        // and we're in a HOLD, REVIEW, PURCHASING or CHARGED cart state we\n        // probably need to kick the cart back to READY here (or maybe kill\n        // it if it was in REVIEW)?\n\n        return id(new AphrontRedirectResponse())->setURI($cancel_uri);\n      }\n    }\n\n    if ($is_refund) {\n      $title = pht('Refund Order?');\n      $body = pht('Really refund this order?');\n      $button = pht('Refund Order');\n      $cancel_text = pht('Cancel');\n\n      $form = id(new AphrontFormView())","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/phortune/controller/cart/PhortuneCartCancelController.php#L134-L170","documentation":"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.","triggerScenarios":"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.","commonSituations":"Partial manual refunds done outside Phortune desynchronizing balances; transient gateway outages mid-cancel; charge-hold corner cases during double-clicks.","solutions":["Reconcile each charge's refund state in Phortune against the gateway's transaction history","Fix the underlying provider failure (credentials, connectivity) and retry the cancel/refund","Complete any remainder as a gateway-side refund and record it manually so balances match"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Pre-check that total refundable balance covers the requested refund\n$refundable_total = 0;\nforeach ($charges as $charge) {\n  if (!$charge->isRefund()) {\n    $refundable_total += $charge->getAmountRefundable()->getValue();\n  }\n}\nif ($refundable_total < $refund->getValue()) {\n  throw new Exception('Insufficient refundable balance to cover the refund.');\n}","typeGuard":null,"tryCatchPattern":"try {\n  // cancel + refund flow\n} catch (Exception $ex) {\n  if (strpos($ex->getMessage(), 'Unable to refund some charges') !== false) {\n    // re-render the cart/charge table so operators see which charges remain unrefunded\n  }\n}","preventionTips":["Reconcile Phortune charge balances with gateway history before refunding","Never refund externally in the gateway dashboard without recording it in Phortune","Monitor provider health and credentials so refund calls do not fail mid-flow"],"tags":["phabricator","phortune","payments","refund","partial-failure"],"backgroundTag":"partial-refund-failure","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}