phacility/phabricator · critical · Exception

Stripe charge call did not return an ID!

Error message

Stripe charge call did not return an ID!

What it means

Exception thrown by PhortuneStripePaymentProvider::executeCharge() after Stripe_Charge::create() returns an object whose ->id is falsy. The charge ID is the only durable reference Phortune stores (as 'stripe.chargeID' metadata) to later refund or update the charge, so losing it means a possibly-successful real charge exists at Stripe with no local handle — the worst state for a payment system. The check exists because the legacy Stripe SDK can return malformed/empty objects (or mocks can) without throwing.

Source

Thrown at src/applications/phortune/provider/PhortuneStripePaymentProvider.php:149

    PhortuneCharge $charge) {
    $this->loadStripeAPILibraries();

    $price = $charge->getAmountAsCurrency();

    $secret_key = $this->getSecretKey();
    $params = array(
      'amount'      => $price->getValueInUSDCents(),
      'currency'    => $price->getCurrency(),
      'customer'    => $method->getMetadataValue('stripe.customerID'),
      'description' => $charge->getPHID(),
      'capture'     => true,
    );

    $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

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Immediately reconcile: search the Stripe dashboard (or API) for a charge whose description equals the Phortune charge PHID before deciding whether money moved; never blind-retry the create call.
  2. Upgrade the vendored Stripe PHP library to a version matching your Stripe API version and retest in sandbox mode.
  3. If the charge truly was not created (dashboard shows nothing), retry the charge flow from the start with a fresh Phortune charge.
  4. If it was created, record its ID onto the charge metadata manually, then let the normal apply-charge path continue.

Example fix

// before
$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!'));
}

// after: verify, reconcile, and only then record
$stripe_charge = Stripe_Charge::create($params, $secret_key);
$id = idx($stripe_charge instanceof Stripe_Object ? $stripe_charge->__toArray() : array(), 'id');
if (!$id) {
  // Charge state unknown: look it up by description (charge PHID) before retrying.
  phlog(pht('Stripe charge for %s returned no ID; manual reconciliation required.', $charge->getPHID()));
  throw new Exception(
    pht('Stripe charge call did not return an ID for %s; reconcile in Stripe.',
      $charge->getPHID()));
}
$charge->setMetadataValue('stripe.chargeID', $id);
$charge->save();
Defensive patterns

Strategy: try-catch

Validate before calling

// Shape-check the SDK result before relying on it (mirrors the provider's
// own guard, but lets you branch instead of catching).
function stripe_charge_has_id($stripe_charge) {
  return is_object($stripe_charge) && !empty($stripe_charge->id);
}

$stripe_charge = Stripe_Charge::create($params, $secret_key);
if (!stripe_charge_has_id($stripe_charge)) {
  // Unknown money state: reconcile by description (charge PHID) in the
  // Stripe dashboard before any retry. DO NOT blind-retry the create.

Type guard

function stripe_charge_has_id($stripe_charge) {
  return is_object($stripe_charge) && !empty($stripe_charge->id);
}

Try / catch

try {
  $provider->executeCharge($method, $charge);
} catch (Exception $ex) {
  if (strpos($ex->getMessage(), 'did not return an ID') !== false) {
    // Charge state unknown: the Phortune charge PHID was sent as the Stripe
    // 'description', so a real charge (if any) is findable by it.
    phlog(pht(
      'UNRESOLVED STRIPE CHARGE %s: reconcile by description before retry.',
      $charge->getPHID()));
  }
  throw $ex;
}

Prevention

When it happens

Trigger: Stripe_Charge::create($params, $secret_key) succeeding at the HTTP level but the response lacking an id — old stripe-php library versions, unexpected API response shape from version drift between the bundled SDK and Stripe's API, or test doubles returning bare objects. Because the description param carries the Phortune charge PHID, an untracked charge can be found later in Stripe's dashboard by that description.

Common situations: Long-lived Phabricator installs with an outdated vendored Stripe library after Stripe deprecated API versions; sandbox mocking in tests that returns stdClass instead of Stripe_Charge with an id; proxy/charset corruption of the JSON response.

Related errors


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