Farpost · Payments
$ The Fix That Almost Reopened the Bug It Fixed
Caching a customer's Stripe ID fixed a real race: Stripe's customer-search API is eventually consistent, so searching for "does this customer already exist" right after creating them could miss it and create a duplicate. But caching introduced a new, quieter bug — if that customer is later deleted directly in the Stripe dashboard, the cached ID points at nothing, and invoicing breaks with a "No such customer" error.
The fix isn't to stop caching — that would reopen the original race. It's to verify the cached ID before trusting it, using a lookup method that doesn't share the same consistency problem the cache was built to avoid.
async def resolve_requestor_customer_id(
cached_customer_id: str | None, email: str, name: str, metadata: dict,
) -> str:
"""Return a valid Stripe customer ID for a requestor, verifying a cached ID still
resolves before trusting it. A cached ID can go stale if the customer is deleted
directly in Stripe. customers.retrieve() is an exact, instant-consistent lookup by
ID, so verifying here doesn't reintroduce the eventually-consistent-search race
the original caching fix was protecting against."""
client = _client()
if cached_customer_id:
try:
client.customers.retrieve(cached_customer_id)
return cached_customer_id
except stripe.error.InvalidRequestError:
logger.warning('{"event": "stripe_customer_id_stale", "customer_id": "%s"}',
cached_customer_id)
return await get_or_create_requestor_customer(email=email, name=name, metadata=metadata)The fix
customers.retrieve() looks a customer up by exact ID, which Stripe guarantees is instantly consistent — unlike the search API. Verifying a cached ID with retrieve() before using it catches a stale, deleted ID without reintroducing the eventual-consistency race the cache was originally built to solve. A retrieve() failure falls back to the same get_or_create path used when there was no cached ID at all.
Why this matters
This is a fix within a fix — it required actually understanding why the original caching fix worked (an exact-ID lookup being consistent when a search isn't) well enough to add a new safety check without silently undoing that reasoning. That's root-cause diagnosis applied a second time, to a problem the first fix itself created.
