Stirling-Tools/Stirling-PDF · error · StripeFunctionError

payg_upsert_bundle_quote returned no row

Error message

payg_upsert_bundle_quote returned no row

What it means

Thrown by upsertBundleQuote when the payg_upsert_bundle_quote RPC resolves without error but returns zero rows. The helper indexes rows?.[0] and treats an empty result set as a contract failure — the function is expected to return exactly one quote row.

Source

Thrown at frontend/editor/src/portal/billing/stripe.ts:173

  input: BundleQuoteInput,
): Promise<BundleQuote> {
  const rows = await rpc<BundleQuoteRow[]>("payg_upsert_bundle_quote", {
    p_team_id: input.teamId,
    p_posture_policies: input.posturePolicies,
    p_size_mult: input.sizeMult,
    p_pipeline_mult: input.pipelineMult,
    p_pool_credits: input.poolCredits,
    p_users: input.users,
    p_provisioned_monthly_volume: input.provisionedMonthlyVolume,
    p_price_minor: input.priceMinor,
    p_currency: input.currency,
    p_consented: input.consented,
    p_eula_version: input.eulaVersion,
    ...(input.quoteId != null ? { p_quote_id: input.quoteId } : {}),
  });
  const row = rows?.[0];
  if (!row) {
    throw new StripeFunctionError("payg_upsert_bundle_quote returned no row");
  }
  return {
    quoteId: row.quote_id,
    status: row.status,
    validUntil: row.valid_until,
  };
}

/** A team's latest open bundle quote — {@code payg_get_latest_bundle_quote} result, for resume. */
export interface LatestBundleQuote {
  quoteId: number;
  users: number | null;
  posturePolicies: number;
  sizeMult: number;
  pipelineMult: number;
  poolCredits: number;
  priceMinor: number | null;
  currency: string | null;

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Inspect the SQL function payg_upsert_bundle_quote — find branches that return no row and make them raise a meaningful exception instead.
  2. Validate inputs before calling (teamId > 0, non-empty currency, priceMinor >= 0, consented flag).
  3. Check the team exists and the caller's JWT has access to it (RLS/SECURITY DEFINER grant).
  4. Re-run the RPC in the Supabase SQL editor with the same args to see the empty result and adjust.

Example fix

// SQL function — before
INSERT INTO payg_bundle_quote (...) VALUES (...) RETURNING *;
-- on conflict or guard, returns nothing

// after — raise instead of silently returning empty
IF p_users <= 0 THEN
  RAISE EXCEPTION 'invalid user count: %', p_users;
END IF;
INSERT ... RETURNING *;
Defensive patterns

Strategy: validation

Validate before calling

function isValidQuoteInput(i: BundleQuoteInput): boolean {
  return (
    i.teamId > 0 &&
    i.users > 0 &&
    typeof i.currency === "string" && i.currency.length === 3 &&
    Number.isFinite(i.priceMinor) && i.priceMinor >= 0
  );
}

if (!isValidQuoteInput(input)) {
  throw new Error("Invalid quote input — refusing to call payg_upsert_bundle_quote.");
}
await upsertBundleQuote(input);

Type guard

function isQuoteRow(row: unknown): row is { quote_id: number; status: string; valid_until: string } {
  return typeof row === "object" && row !== null &&
    typeof (row as any).quote_id === "number";
}

Try / catch

try {
  await upsertBundleQuote(input);
} catch (e) {
  if (e instanceof StripeFunctionError && /returned no row/i.test(e.message)) {
    notifications.show({ message: "Quote could not be created. Check team and pricing.", color: "red" });
  } else throw e;
}

Prevention

When it happens

Trigger: The SQL function payg_upsert_bundle_quote returns no row — e.g. an input validation inside the function rejected the payload (returning nothing instead of raising), a conflict path that returns empty, or a RETURNING clause that matched nothing.

Common situations: Passing an invalid team_id, a currency/price the function rejects, or a consented/EULA-version combination the function gates on; a DB-level constraint that silently short-circuits; the function was redeployed with a bug in its RETURNING.

Related errors


AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13). Data as JSON: /api/errors/da379da52a16b07e. Report an issue: GitHub.