障害・修正記録

「払ったのにログインできない」を
生む決済バグを見つけて直した。

自分たちのteai.ioでLLM実験の予算を使い切り、実際にクレジットをチャージしようとして気づいた。Proプランの決済ボタンは、静的なStripe決済リンクに直接つながっているだけで、「誰が払ったか」をteai.io側に伝える仕組みが一切なかった。決済時のメールアドレスと登録メールが1文字でも違えば、支払いは成立するのにクレジットは誰のアカウントにも増えない。今日、この欠陥を発見してその場で直した記録。

発見 2026-08-17
影響範囲 teai.io Proプラン決済(SMB/Businessは別導線)
修正 同日デプロイ・本番確認済み

結論から3行: ①Proプランの決済リンクはuser_idを一切運ばない静的URLだった。②webhook側は最終手段としてメール完全一致で本人を推測し、失敗すると無言で孤児アカウントを作る設計だった。③認証必須のcheckout API経由に切り替え、実user_idを直接紐付けることで解決した。同日中に実機で確認済み。

気づいたきっかけ

今日はteai/maxに未解決の数学予想を解かせる実験をしていて、実際にteai.ioの自分のアカウントのクレジットを使い切った。「じゃあ自分でチャージしよう」とダッシュボードの「チャージ」リンクをたどったところ、Pro/SMB/Businessという月額プランしかなく、しかもProボタンはhttps://buy.stripe.com/...という固定URLに飛ぶだけだった。「これ、本当に自分のアカウントに反映されるのか?」と疑問に思い、コードを読んだ。

見つかった欠陥

Stripeの「Payment Link」は、URLを配るだけで使える決済リンクだ。手軽な反面、誰がそのURLを踏んだかをアプリ側に伝える仕組みを自分で用意しない限り、決済完了時にteai.io側は「誰が払ったか」を一切知らない。webhook(checkout.session.completed)を受け取った時の本人特定は、次の順で試みられていた:

1. client_reference_id (Stripeチェックアウト作成時に付与できるID) → 常に空 2. metadata.session_key → 常に空 3. 決済時のメールアドレスで、teai.io登録メールを完全一致検索 4. 1〜3すべて失敗 → 新規の匿名アカウントをその場で作成し、そこにプラン・クレジットを付与
実害のシナリオ 会社のクレジットカードで法人メールを使って決済した、Stripeが自動補完したメールが少し違った、大文字小文字が違った——理由は何であれ3が外れると、4に落ちて誰にも紐付かない孤児アカウントが生まれる。支払った本人からは「クレジットカードは引き落とされたのに、teai.ioにログインしても何も増えていない」という不具合にしか見えない。

直した内容

検証

$ curl -X POST https://api.teai.io/api/v1/billing/checkout -d '{"plan":"pro"}' → 401 Unauthorized (未認証は拒否されることを確認) $ curl -X POST https://api.teai.io/api/v1/billing/checkout \ -H "Authorization: Bearer $TOKEN" -d '{"plan":"pro"}' → {"checkout_url":"https://checkout.stripe.com/c/pay/cs_live_...","plan":"pro"} (実際に本番のStripe live checkoutセッションが発行されることを確認) $ curl -s https://teai.io/pricing | grep -c "buy.stripe.com" → 0 (静的リンクが完全に消えたことを確認)

バックエンド側のユニットテスト(webhookの本人解決ロジック)21件も全てgreenであることを確認済み。

教訓 Stripeの「Payment Link」は導入が一番簡単な決済方法だが、その手軽さと引き換えに「誰が払ったか」の紐付けを自分で設計しないといけない。SaaSでこの手のリンクを使う時は、必ず「決済メールとアプリ内アカウントが一致しなかったらどうなるか」を最初に問うべきだった。

teai.ioを無料で試す

クレジットカード不要・クーポン「WELCOME」で100クレジット。

無料で始める 他の記事を見る