{"record":{"id":"4467e6647335105f","repo":"phacility/phabricator","slug":"this-order-can-not-be-voided-because-it-is-not-rea","errorCode":null,"errorMessage":"This order can not be voided because it is not ready for payment.","messagePattern":"This order can not be voided because it is not ready for payment\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"warning","filePath":"src/applications/phortune/storage/PhortuneCart.php","lineNumber":554,"sourceCode":"          pht(\n            'This order can not be refunded because it has not been placed.'));\n    }\n\n    return $this->getImplementation()->assertCanRefundOrder($this);\n  }\n\n  public function assertCanVoidOrder() {\n    if (!$this->getIsInvoice()) {\n      throw new Exception(\n        pht(\n          'This order can not be voided because it is not an invoice.'));\n    }\n\n    switch ($this->getStatus()) {\n      case self::STATUS_READY:\n        break;\n      default:\n        throw new Exception(\n          pht(\n            'This order can not be voided because it is not ready for '.\n            'payment.'));\n    }\n\n    return null;\n  }\n\n\n  protected function getConfiguration() {\n    return array(\n      self::CONFIG_AUX_PHID => true,\n      self::CONFIG_SERIALIZATION => array(\n        'metadata' => self::SERIALIZATION_JSON,\n      ),\n      self::CONFIG_COLUMN_SCHEMA => array(\n        'status' => 'text32',\n        'cartClass' => 'text128',","sourceCodeStart":536,"sourceCodeEnd":572,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/phortune/storage/PhortuneCart.php#L536-L572","documentation":"assertCanVoidOrder() throws when an invoice cart's status is anything other than STATUS_READY: an invoice may only be voided while it is still awaiting payment. Once it has been paid (or is otherwise in flight), the correct remedy is a refund, not a void. The READY-only rule keeps the invoice lifecycle consistent.","triggerScenarios":"Calling assertCanVoidOrder() on an invoice cart whose status is not PhortuneCart::STATUS_READY - for example one that was already charged or is processing.","commonSituations":"Attempting to void an invoice after the subscription already auto-charged it; race between the billing worker charging the invoice and a manager voiding it; retrying a void on an already-voided or processed invoice.","solutions":["Check status (or wrap in canVoidOrder-style logic) and only offer void while the invoice awaits payment.","If the invoice was already paid, use the refund flow instead of void.","When auto-billing is enabled for a subscription, pause billing before voiding to avoid the charge/void race."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if ($cart->getIsInvoice() &&\n    $cart->getStatus() !== PhortuneCart::STATUS_READY) {\n  // invoice no longer awaiting payment: use refund instead of void\n}","typeGuard":"function isVoidableInvoiceCart(PhortuneCart $cart) {\n  return (bool)$cart->getIsInvoice()\n    && $cart->getStatus() === PhortuneCart::STATUS_READY;\n}","tryCatchPattern":"try {\n  $cart->assertCanVoidOrder();\n} catch (Exception $ex) {\n  // show $ex->getMessage(); if already paid, route the user to refund instead\n}","preventionTips":["Only allow void while the invoice status is STATUS_READY.","Pause subscription auto-billing before voiding to avoid the charge/void race.","Route already-paid invoices to the refund flow."],"tags":["phortune","phabricator","php","payments","cart","invoice","order-lifecycle"],"backgroundTag":"invalid-cart-status","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}