fix(BUG-106): DB fallback for downgradeByCustomer and recover route
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 13m7s

- downgradeByCustomer now queries DB when key not in memory cache,
  preventing cancelled customers from keeping Pro access in multi-pod setups
- recover/verify endpoint falls back to DB lookup when cache miss on email
- Added TDD tests for both fallback paths (4 new tests)
This commit is contained in:
OpenClaw 2026-03-06 20:06:04 +01:00
parent 4473641ee1
commit b964b98a8b
4 changed files with 240 additions and 2 deletions

View file

@ -134,7 +134,33 @@ export async function downgradeByCustomer(stripeCustomerId: string): Promise<boo
await queryWithRetry("UPDATE api_keys SET tier = 'free' WHERE stripe_customer_id = $1", [stripeCustomerId]);
return true;
}
return false;
// DB fallback: key may exist on another pod's cache or after a restart
logger.info({ stripeCustomerId }, "downgradeByCustomer: cache miss, falling back to DB");
const result = await queryWithRetry(
"SELECT key, tier, email, created_at, stripe_customer_id FROM api_keys WHERE stripe_customer_id = $1 LIMIT 1",
[stripeCustomerId]
);
if (result.rows.length === 0) {
logger.warn({ stripeCustomerId }, "downgradeByCustomer: customer not found in cache or DB");
return false;
}
const row = result.rows[0];
await queryWithRetry("UPDATE api_keys SET tier = 'free' WHERE stripe_customer_id = $1", [stripeCustomerId]);
// Add to local cache so subsequent lookups on this pod work
const cached: ApiKey = {
key: row.key,
tier: "free",
email: row.email,
createdAt: row.created_at instanceof Date ? row.created_at.toISOString() : row.created_at,
stripeCustomerId: row.stripe_customer_id || undefined,
};
keysCache.push(cached);
logger.info({ stripeCustomerId, key: row.key }, "downgradeByCustomer: downgraded via DB fallback");
return true;
}
export async function findKeyByCustomerId(stripeCustomerId: string): Promise<ApiKey | null> {