phacility/phabricator · error · Exception
Unable to refund charge; no Stripe chargeID!
Error message
Unable to refund charge; no Stripe chargeID!
What it means
Exception thrown by PhortuneStripePaymentProvider::executeRefund() when the charge to refund has no 'stripe.chargeID' metadata value. That metadata is written only by executeCharge() after a successful Stripe_Charge::create(); without it there is no Stripe charge object to call refunds->create() on. Like the PayPal twin, this is a precondition check that fires before any Stripe API call, meaning Phortune considers this charge not (provably) paid via Stripe.
Source
Thrown at src/applications/phortune/provider/PhortuneStripePaymentProvider.php:163
$stripe_charge = Stripe_Charge::create($params, $secret_key);
$id = $stripe_charge->id;
if (!$id) {
throw new Exception(pht('Stripe charge call did not return an ID!'));
}
$charge->setMetadataValue('stripe.chargeID', $id);
$charge->save();
}
protected function executeRefund(
PhortuneCharge $charge,
PhortuneCharge $refund) {
$this->loadStripeAPILibraries();
$charge_id = $charge->getMetadataValue('stripe.chargeID');
if (!$charge_id) {
throw new Exception(
pht('Unable to refund charge; no Stripe chargeID!'));
}
$refund_cents = $refund
->getAmountAsCurrency()
->negate()
->getValueInUSDCents();
$secret_key = $this->getSecretKey();
$params = array(
'amount' => $refund_cents,
);
$stripe_charge = Stripe_Charge::retrieve($charge_id, $secret_key);
$stripe_refund = $stripe_charge->refunds->create($params);
$id = $stripe_refund->id;
if (!$id) {View on GitHub (pinned to 5720a38cfe)
Solutions
- Refund only charges in CHARGED status that were paid via the Stripe provider.
- Check the charge's provider and metadata before building the refund; route PayPal charges to the PayPal provider.
- If a real Stripe charge exists (findable by the charge PHID stored as the Stripe charge description), restore 'stripe.chargeID' metadata and retry.
- For never-completed charges, void/fail them instead of refunding.
Example fix
// before
$provider->executeRefund($charge, $refund);
// after
$charge_id = $charge->getMetadataValue('stripe.chargeID');
if ($charge->getStatus() !== PhortuneCharge::STATUS_CHARGED || !strlen((string)$charge_id)) {
throw new Exception(
pht('Charge %s has no Stripe charge on record; cannot refund.',
$charge->getPHID()));
}
$provider->executeRefund($charge, $refund); Defensive patterns
Strategy: validation
Validate before calling
// Preconditions for a Stripe refund.
function stripe_charge_is_refundable(PhortuneCharge $charge) {
if ($charge->getStatus() !== PhortuneCharge::STATUS_CHARGED) {
return false;
}
return strlen((string) $charge->getMetadataValue('stripe.chargeID')) > 0;
}
if (!stripe_charge_is_refundable($charge)) {
// skip: no Stripe charge object exists to refund against
} Type guard
function charge_has_stripe_charge_id(PhortuneCharge $charge) {
$id = $charge->getMetadataValue('stripe.chargeID');
return is_string($id) && strlen($id) > 0;
} Try / catch
try {
$provider->executeRefund($charge, $refund);
} catch (Exception $ex) {
// Missing chargeID is unrecoverable via retry: log the charge PHID for
// manual reconciliation and keep the refund record pending.
phlog(pht('Stripe refund blocked for %s: %s', $charge->getPHID(), $ex->getMessage()));
throw $ex;
} Prevention
- Refund only CHARGED charges through the provider that charged them.
- Route refunds by the charge's stored provider metadata, not by whichever provider is configured first.
- If repairing data, recover the Stripe charge ID via the dashboard (search by charge PHID description) and restore metadata before retrying.
When it happens
Trigger: Refunding a charge whose status is HOLD/FAIL (executeCharge never completed, no stripe.chargeID saved); a charge paid through a different provider being refunded through the Stripe provider; metadata stripped by manual database edits or a crash between charge creation and save().
Common situations: Refund scripts iterating all charges regardless of provider or status; test charges created directly in the DB without going through executeCharge; partial state after an exception between the Stripe call and $charge->save().
Related errors
- Charge has no transaction ID!
- Unable to update charge; no Stripe chargeID!
- Stripe charge call did not return an ID!
- Stripe refund call did not return an ID!
- Trying to refund non-positive amount of money!
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/64be32244c4786d7.
Report an issue: GitHub.