Fixing Stripe 'No such price' with mismatched account keys
The checkout flow threw a "No such price" error. The secret key in the server .env belonged to a different Stripe account than the publishable key and price IDs.
Two accounts, one LLC
The secret key on the server was from the MemStack account. The publishable key and price IDs belonged to the EpsteinScan account. Both accounts are under the same LLC, so the key was valid but pointed to the wrong business.
I verified the correct EpsteinScan secret key matched the publishable key. Both price IDs resolved. The webhook endpoint was present and enabled with the correct events. A test session for the Unlimited plan returned a checkout URL for $15.00.
Search punctuation and video paths
Search queries with punctuation returned false empties. I added FTS token quoting in search_documents. Each whitespace-split token is quoted before the MATCH query.
Videos were not loading in production. VIDEO_BASE defaulted based on IS_PRODUCTION, but production ran with PRODUCTION=0. I changed VIDEO_BASE to default to the absolute videos path unconditionally, so it ships via git and does not depend on the flag.
I added ds10 to the video scan, filter, and stream logic. I added safe_join to the image and download routes to block path traversal.
Pricing page and navigation
I rebuilt the pricing page from two tiers to three. Free is $0, Pro is $7 with 50 AI searches per day, Unlimited is $15. The Pro tier previously claimed unlimited searches. I wired the Unlimited CTA to the existing create-checkout-session POST with plan=unlimited.
Anonymous users clicking upgrade buttons now route to /signup?next=/pricing instead of login. The auth forms already carry next through the POST.
I added an open-redirect guard to signup and login. The _safe_next function only follows next if it starts with / and not // or /backslash.
EFTA exact lookup and news generation
The AI Analyst now does an exact EFTA lookup before the vector search. I added Pass 0 in gather_context that regex-extracts EFTA IDs, handles spaces and lowercase, does exact filename LIKE lookup, prepends matches, and notes missing IDs. gather_context now returns docs and efta_missing so the AI can say a document is not in the archive.
I added news category blog generation to auto_blog.py. gather_news pulls from monitor_items, the same source as the newsletter. A blog_used column tracks what has been posted. About one in four daily posts is news, determined by day-of-year mod 4. If no news is available, the script falls back to a document post. I wrote and inserted the first news post into the shared database.
I changed the Dark Networks button on the homepage from an Amazon affiliate link to a link to /books. Both book covers now link to /books, removing the last Amazon affiliate link from that card.