phacility/phabricator · error · Exception

Cart is already charging!

Error message

Cart is already charging!

What it means

Exception thrown by PhortunePayPalPaymentProvider::processControllerRequest() when the 'checkout' action arrives but loadActiveCharge($cart) already returns a charge. Phortune's state machine requires exactly one in-flight charge per cart: starting a new PayPal Express Checkout while an existing charge is active would create a second concurrent payment attempt for the same cart. This guard is an invariant check that stops double-checkout before SetExpressCheckout is ever called.

Source

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

    return parent::canRespondToControllerAction();
  }

  public function processControllerRequest(
    PhortuneProviderActionController $controller,
    AphrontRequest $request) {

    $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(),
          ));

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Navigate to the cart's checkout URI and resume the existing in-flight charge instead of initiating a new checkout.
  2. Fail or cancel the existing active charge (e.g. via the 'cancel' provider action) before starting a new checkout attempt.
  3. In UI code, disable the pay button once the cart transitions to STATUS_PURCHASING so the second initiation never happens.

Example fix

// before
// user re-clicks "Checkout with PayPal" while a charge is active
// -> Exception('Cart is already charging!')

// after: in the checkout controller, short-circuit in-flight carts
if ($cart->getStatus() === PhortuneCart::STATUS_PURCHASING) {
  return id(new AphrontRedirectResponse())
    ->setURI($cart->getCheckoutURI()); // resume existing charge
}
Defensive patterns

Strategy: validation

Validate before calling

// Before initiating a provider checkout, ensure no charge is in flight.
function cart_can_start_checkout(PhortuneCart $cart) {
  return $cart->getStatus() === PhortuneCart::STATUS_READY;
}

if (!cart_can_start_checkout($cart)) {
  // PURCHASING cart: resume the existing charge instead of starting a new one
  return id(new AphrontRedirectResponse())
    ->setURI($cart->getCheckoutURI());
}
$provider->processControllerRequest($controller, $request);

Try / catch

try {
  $response = $provider->processControllerRequest($controller, $request);
} catch (Exception $ex) {
  // 'Cart is already charging!' is an invariant breach: send the user back
  // to the cart rather than showing a raw error; log for double-click analysis.
  phlog($ex->getMessage());
  return id(new AphrontRedirectResponse())
    ->setURI($cart->getCheckoutURI());
}

Prevention

When it happens

Trigger: A user clicks 'Pay with PayPal', then re-visits/reloads the provider checkout initiation URL while the first charge is still active (cart in STATUS_PURCHASING); double-click on the checkout submit; a stale browser tab replaying the checkout GET; automated hitting of the checkout route for an in-flight cart.

Common situations: Impatient buyers clicking pay twice; back-button navigation into the checkout start point after PayPal already redirected; QA scripts re-running checkout steps against the same cart; browser prefetch touching the checkout URL.

Related errors


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