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

  1. Refund only charges in CHARGED status that were paid via the Stripe provider.
  2. Check the charge's provider and metadata before building the refund; route PayPal charges to the PayPal provider.
  3. If a real Stripe charge exists (findable by the charge PHID stored as the Stripe charge description), restore 'stripe.chargeID' metadata and retry.
  4. 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

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


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