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
- Navigate to the cart's checkout URI and resume the existing in-flight charge instead of initiating a new checkout.
- Fail or cancel the existing active charge (e.g. via the 'cancel' provider action) before starting a new checkout attempt.
- 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
- Disable the pay button as soon as the cart leaves STATUS_READY.
- Treat the provider 'checkout' action as idempotent in wrappers: in-flight cart -> redirect to checkout URI.
- Cancel the active charge before allowing a deliberate retry of payment.
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
- Cart is not charging yet!
- Charge has no transaction ID!
- Paypal checkout does not match Phortune charge!
- Charge is in the wrong refunding state!
- This order can not be cancelled because the application has
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/1dfc4d73020a85c6.
Report an issue: GitHub.