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
- Send the user to the cart's checkout URI (cart->getCheckoutURI()) — the flow there already handles resolved carts gracefully with redirects instead of exceptions.
- 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.
- 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
- Accept that buyers refresh return URLs; design wrappers to redirect resolved carts instead of throwing.
- Never link or bookmark provider charge/cancel endpoints — link the cart checkout URI.
- After didApplyCharge/didFailCharge, the active charge is consumed: any further callback hits this path.
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
- Cart is already charging!
- 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/94bf7cb3535b3b17.
Report an issue: GitHub.