phacility/phabricator · error · Exception

Cart is not charging yet!

Error message

Cart is not charging yet!

What it means

Exception thrown by PhortunePayPalPaymentProvider::processControllerRequest() when the 'charge' or 'cancel' action arrives but loadActiveCharge($cart) finds no active charge. These actions are PayPal's return/cancel callbacks: they only make sense in the middle of an initiated checkout that created a charge. Arriving without one means the callback is being replayed or hit directly after the charge already resolved (applied, held, or failed) and was consumed.

Source

Thrown at src/applications/phortune/provider/PhortunePayPalPaymentProvider.php:308

    $viewer = $request->getUser();

    $cart = $controller->loadCart($request->getInt('cartID'));
    if (!$cart) {
      return new Aphront404Response();
    }

    $charge = $controller->loadActiveCharge($cart);
    switch ($controller->getAction()) {
      case 'checkout':
        if ($charge) {
          throw new Exception(pht('Cart is already charging!'));
        }
        break;
      case 'charge':
      case 'cancel':
        if (!$charge) {
          throw new Exception(pht('Cart is not charging yet!'));
        }
        break;
    }

    switch ($controller->getAction()) {
      case 'checkout':
        $return_uri = $this->getControllerURI(
          'charge',
          array(
            'cartID' => $cart->getID(),
          ));

        $cancel_uri = $this->getControllerURI(
          'cancel',
          array(
            'cartID' => $cart->getID(),
          ));

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Send the user to the cart's checkout URI (cart->getCheckoutURI()) — the flow there already handles resolved carts gracefully with redirects instead of exceptions.
  2. In custom controllers wrapping these actions, pre-check for an active charge and issue a redirect response for the no-charge case rather than letting the provider throw.
  3. Accept idempotent callbacks: treat a second 'charge' hit on a purchased cart as a no-op redirect.

Example fix

// before
// GET /phortune/paypal/charge/?cartID=42&token=... a second time
// -> Exception('Cart is not charging yet!')

// after: guard the callback before dispatching to the provider
$charge = $controller->loadActiveCharge($cart);
if (!$charge) {
  return id(new AphrontRedirectResponse())
    ->setURI($cart->getCheckoutURI());
}
$response = $provider->processControllerRequest($controller, $request);
Defensive patterns

Strategy: validation

Validate before calling

// Guard the PayPal return/cancel callbacks before dispatch.
$charge = $controller->loadActiveCharge($cart);
$action = $controller->getAction();
if ($action === 'charge' || $action === 'cancel') {
  if (!$charge) {
    // Replay/direct hit after the charge already resolved: redirect gracefully.
    return id(new AphrontRedirectResponse())
      ->setURI($cart->getCheckoutURI());
  }
}
$response = $provider->processControllerRequest($controller, $request);

Try / catch

try {
  $response = $provider->processControllerRequest($controller, $request);
} catch (Exception $ex) {
  if (strpos($ex->getMessage(), 'Cart is not charging') !== false) {
    // Idempotent replay of the return URL: show the cart, not an error.
    return id(new AphrontRedirectResponse())
      ->setURI($cart->getCheckoutURI());
  }
  throw $ex;
}

Prevention

When it happens

Trigger: PayPal redirecting the buyer back to the return URL twice (refresh of the 'charge' page); a user bookmarking or re-opening the charge/cancel URL after completion; the cart status no longer being PURCHASING with the prior charge already resolved via didApplyCharge/didFailCharge; direct scraping/manual requests to those endpoints.

Common situations: Buyer refreshing the PayPal return landing page; email/IM sharing of the return URL; crawlers following redirect chains; double-fired redirects from PayPal sandbox flakiness.

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/94bf7cb3535b3b17. Report an issue: GitHub.