{"record":{"id":"23b0583fd6013e7d","repo":"phacility/phabricator","slug":"trying-to-refund-non-positive-amount-of-money","errorCode":null,"errorMessage":"Trying to refund non-positive amount of money!","messagePattern":"Trying to refund non-positive amount of money!","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/phortune/storage/PhortuneCart.php","lineNumber":315,"sourceCode":"        // Move the cart back into STATUS_READY so the user can try\n        // making the purchase again.\n        $this->setStatus(self::STATUS_READY)->save();\n\n      $this->endReadLocking();\n    $this->saveTransaction();\n\n    return $this;\n  }\n\n\n  public function willRefundCharge(\n    PhabricatorUser $actor,\n    PhortunePaymentProvider $provider,\n    PhortuneCharge $charge,\n    PhortuneCurrency $amount) {\n\n    if (!$amount->isPositive()) {\n      throw new Exception(\n        pht('Trying to refund non-positive amount of money!'));\n    }\n\n    if ($amount->isGreaterThan($charge->getAmountRefundableAsCurrency())) {\n      throw new Exception(\n        pht('Trying to refund more money than remaining on charge!'));\n    }\n\n    if ($charge->getRefundedChargePHID()) {\n      throw new Exception(\n        pht('Trying to refund a refund!'));\n    }\n\n    if (($charge->getStatus() !== PhortuneCharge::STATUS_CHARGED) &&\n        ($charge->getStatus() !== PhortuneCharge::STATUS_HOLD)) {\n      throw new Exception(\n        pht('Trying to refund an uncharged charge!'));\n    }","sourceCodeStart":297,"sourceCodeEnd":333,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/phortune/storage/PhortuneCart.php#L297-L333","documentation":"PhortuneCart::willRefundCharge() validates the refund amount first and throws when the PhortuneCurrency passed is zero or negative. Refund amounts must be strictly positive because the refund charge record is stored with the amount negated. A non-positive value therefore means the caller computed or parsed the amount incorrectly.","triggerScenarios":"Calling willRefundCharge() with an amount parsed from empty input (e.g. PhortuneCurrency::newFromString('0.00')), a negative value produced by sign arithmetic, or a currency whose value was never set.","commonSituations":"Refund forms submitted with a blank amount; proration math that yields zero for the final period; reusing a negated refund currency as input to a second refund.","solutions":["Validate $amount->isPositive() before calling and reject zero-value submissions in the form with a field error.","Build amounts with PhortuneCurrency::newFromUserInput()/newFromString() and re-validate after parsing.","Skip the refund entirely when a computed amount rounds down to zero."],"exampleFix":"// before\n$amount = PhortuneCurrency::newFromUserInput($request->getStr('amount'));\n$refund = $cart->willRefundCharge($actor, $provider, $charge, $amount); // throws on '0.00' or negative\n\n// after\n$amount = PhortuneCurrency::newFromUserInput($request->getStr('amount'));\nif (!$amount->isPositive()) {\n  $errors[] = pht('Refund amount must be greater than zero.');\n} else {\n  $refund = $cart->willRefundCharge($actor, $provider, $charge, $amount);\n}","handlingStrategy":"validation","validationCode":"$amount = PhortuneCurrency::newFromUserInput($raw);\nif (!$amount->isPositive()) {\n  $errors[] = pht('Refund amount must be greater than zero.');\n}\n// only call willRefundCharge() when $errors is empty","typeGuard":null,"tryCatchPattern":"try {\n  $cart->willRefundCharge($actor, $provider, $charge, $amount);\n} catch (Exception $ex) {\n  // surface $ex->getMessage() as a form error; non-positive amounts are caller bugs\n}","preventionTips":["Validate amounts with isPositive() before every refund call.","Parse refund input with PhortuneCurrency helpers rather than manual arithmetic.","Skip zero-value refunds instead of passing them through."],"tags":["phortune","phabricator","php","payments","refund","input-validation"],"backgroundTag":"invalid-refund-amount","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}