{"record":{"id":"62b04228c8d2ccb4","repo":"phacility/phabricator","slug":"this-order-can-not-be-cancelled-because-it-has-alr","errorCode":null,"errorMessage":"This order can not be cancelled because it has already been completed.","messagePattern":"This order can not be cancelled because it has already been completed\\.","errorType":"validation","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/phortune/cart/PhortuneCartImplementation.php","lineNumber":30,"sourceCode":"    PhabricatorUser $viewer,\n    array $carts);\n\n  abstract public function getName(PhortuneCart $cart);\n  abstract public function getCancelURI(PhortuneCart $cart);\n  abstract public function getDoneURI(PhortuneCart $cart);\n\n  public function getDescription(PhortuneCart $cart) {\n    return null;\n  }\n\n  public function getDoneActionName(PhortuneCart $cart) {\n    return pht('Return to Application');\n  }\n\n  public function assertCanCancelOrder(PhortuneCart $cart) {\n    switch ($cart->getStatus()) {\n      case PhortuneCart::STATUS_PURCHASED:\n        throw new Exception(\n          pht(\n            'This order can not be cancelled because it has already been '.\n            'completed.'));\n        break;\n    }\n  }\n\n  public function assertCanRefundOrder(PhortuneCart $cart) {\n    return;\n  }\n\n  abstract public function willCreateCart(\n    PhabricatorUser $viewer,\n    PhortuneCart $cart);\n\n}\n","sourceCodeStart":12,"sourceCodeEnd":47,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/phortune/cart/PhortuneCartImplementation.php#L12-L47","documentation":"PhortuneCartImplementation::assertCanCancelOrder rejects cancellation when the cart status is STATUS_PURCHASED - a completed order must go through refunds at the charge level, not cancellation. Cancel exists for pre-purchase and holding states; once purchased, the money path is willRefundCharge/didRefundCharge via the provider.","triggerScenarios":"Invoking the cart cancel flow on a cart whose status is already PURCHASED; a product implementation whose UI still shows a Cancel button after checkout completes; admin scripts calling assertCanCancelOrder over historical carts.","commonSituations":"Buyers clicking Cancel on a completed order page; operators attempting to undo a purchase by cancelling; stale carts from testing where status moved on between page load and click.","solutions":["Use the refund flow (refund charges via the provider) instead of cancelling a purchased cart","Check cart status before offering a cancel action - only non-PURCHASED carts may be cancelled","In custom cart implementations, implement assertCanRefundOrder() to expose a refund path for purchased carts"],"exampleFix":"// before\n$implementation->assertCanCancelOrder($cart); // throws on PURCHASED\n// after\nif ($cart->getStatus() === PhortuneCart::STATUS_PURCHASED) {\n  $implementation->assertCanRefundOrder($cart);\n} else {\n  $implementation->assertCanCancelOrder($cart);\n}","handlingStrategy":"validation","validationCode":"// Route by cart status before offering the action\nif ($cart->getStatus() === PhortuneCart::STATUS_PURCHASED) {\n  $implementation->assertCanRefundOrder($cart);\n} else {\n  $implementation->assertCanCancelOrder($cart);\n}","typeGuard":"function canCancelCart(PhortuneCart $cart) {\n  return $cart->getStatus() !== PhortuneCart::STATUS_PURCHASED;\n}","tryCatchPattern":null,"preventionTips":["Show Refund, not Cancel, once a cart reaches PURCHASED status","Check cart status programmatically before calling assertCanCancelOrder","Implement assertCanRefundOrder() in custom cart implementations"],"tags":["phabricator","phortune","payments","cart","order-state"],"backgroundTag":"invalid-state-transition","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}