Yuta Matsufuji

Webアプリケーション / 技術ケーススタディ

DOUNAL

一次情報をもとに意思決定を支えるWebサービスの設計・実装ケース

何を作ったか

投稿、検索、認証、決済、Wallet、Ledger、Unlock、監視・障害追跡の仕組みと運用手順を設計したWebアプリケーションです。

誰向けか

自分に近い人の経験・結果・助言を見て、選択前の判断材料を得たい利用者。

担当範囲

要件整理、情報設計、画面実装、API、DB連携、決済連携、テスト、公開準備までを横断して扱いました。

現在の状態

事業成果ではなく、設計・実装・検証内容を確認するための主力技術ケーススタディとして掲載しています。

種別Webアプリケーション
担当要件整理 / 情報設計 / 画面 / API / DB / 決済 / テスト / 公開準備
主な技術Next.js / TypeScript / Supabase / PostgreSQL / Stripe / Playwright
技術課題決済、台帳、冪等性、権限、監視、CI
  • Next.js
  • React
  • TypeScript
  • Supabase
  • PostgreSQL
  • Supabase Auth
  • Stripe Checkout
  • Stripe Webhook
  • Playwright
  • GitHub Actions
  • Vercel
  • Sentry

実際の画面

公開可能な範囲で確認した実画面です。クリックすると拡大できます。

掲載画面には、開発・検証用に作成したデータを使用しています。

DOUNALの投稿一覧と検索画面 PC表示
投稿一覧、検索、ジャンル絞り込み、比較ボード入口を確認できるPC画面。
DOUNALの投稿一覧と検索画面 モバイル表示
モバイル幅で投稿一覧、検索、投稿導線のレスポンシブ表示を確認できる画面。
DOUNALの投稿詳細画面 PC表示
投稿詳細。無料で読める3項目、解錠後に確認できる内容、ログイン後の解錠導線を確認できる画面。
DOUNALの購入モーダル PC表示
Checkout開始前の購入モーダル。プラン、残高、必要コイン、法務同意、購入前確認を表示する画面。
DOUNALのコイン履歴画面 PC表示
購入履歴、コイン付与、投稿解錠、質問利用履歴をまとめて確認する画面。撮影はダミーデータです。
DOUNALのコイン利用ルール画面 PC表示
有料解錠、質問課金、返金時の確認順など、購入前後の仕様を利用者に説明する画面。

担当範囲

Next.js App Routerの画面とRoute Handler
Supabase AuthとPostgreSQL RPCを使った認証・DB連携
Stripe CheckoutとWebhookを含む購入導線
Wallet / Ledger / Purchase / Unlockのデータ境界
Playwright E2E、GitHub Actions、Vercel cron、Sentry関連の品質・運用導線

システム構成

認証、DB、決済、監視、テストを含むWebアプリケーションとして整理しています。

システム全体
Next.js App Router
Route Handler
Supabase Auth / PostgreSQL
Stripe Checkout / Webhook
Vercel / GitHub Actions
Wallet / Ledger / Purchase / Unlock
wallet_ledger
残高の真実源泉
wallets.balance_coin
purchases
購入履歴
wallet_ledger grant
unlocks
有料開放履歴
wallet_ledger spend
購入から解錠まで
購入モーダル
/api/checkout
Stripe Checkout
Webhook / success復帰
Wallet反映 / Unlock

技術的な見どころ

採用担当者にも流れが分かるよう、実現したかったこと、設計、理由、防いでいる問題、検証方法に分けています。

決済後の整合性

目的: 購入後の残高反映と有料開放を重複なく扱うこと。設計: Checkout、Webhook署名検証、冪等キー、Purchase / Ledger記録、Wallet更新、Unlock判定を分けています。

  • 防いでいる問題: 決済通知の重複受信、同一コンテンツへの再課金、残高と履歴の不一致。
  • 検証: Webhook handler、migration、購入モーダルE2E、決済失敗時のplaybookを確認。

データと権限

目的: 残高、購入履歴、有料開放履歴の参照範囲を分けること。設計: Supabase Auth、RLS、RPCを使い、更新処理をDB側へ寄せています。

  • 防いでいる問題: 画面表示用の残高と追跡用台帳の混同、権限外データの参照、API途中失敗による不整合。
  • 検証: migration、RLS、Route HandlerのRPC呼び出し、Wallet / Ledger / Purchase / Unlockの境界を確認。

障害・運用対策

目的: 決済や解錠の失敗時に原因を追えること。設計: Sentry、Vercel Logs、Webhook failure playbook、refund playbook、admin_actionsを組み合わせています。

  • 防いでいる問題: 失敗原因の追跡不能、返金判断時の記録不足、障害時の確認漏れ。
  • 検証: 監視コード、runbook、playbook、監査ログ記録の導線を確認。

テスト・公開品質

目的: 変更時に画面、型、ビルド、主要導線の崩れを早めに見つけること。設計: TypeScript、Playwright、GitHub Actions、Vercel確認を組み合わせています。

  • 防いでいる問題: 決済前UIの崩れ、Unlock保持の回帰、公開前の型・ビルドエラー見落とし。
  • 検証: package scripts、tests-e2e、GitHub Actions workflow、ビルド手順を確認。

設計判断

台帳を分ける

残高表示用のwalletsと、追跡用のwallet_ledgerを分け、表示速度と後追い確認を両立しています。

RPCへ寄せる

残高確認、消費、Unlock作成をDB側に寄せ、API処理途中の不整合を抑えています。

運用手順を残す

Webhook失敗や返金時の確認手順をplaybook化し、障害時に確認すべき情報を追えるようにしています。

テスト・品質管理

自動確認

  • TypeScript型チェック。
  • 購入モーダル、Unlock保持、decision replay系のPlaywright E2E。
  • GitHub Actionsでlint、typecheck、build。

運用確認

  • Vercel cronで定期処理を構成。
  • SentryとVercel Logsでcritical pathを確認する方針。
  • Webhook失敗、返金対応のplaybookを用意。

コード抜粋

採用担当者が設計意図を確認できる短い抜粋に限定しています。

Stripe Webhook署名検証

目的: Stripeから届いたイベントが正しい送信元のものか確認する。防いでいる問題: なりすまし通知による不正な残高反映。検証: handler、Sentry送信、失敗時レスポンスを確認。参照元: app/api/stripe/webhook/route.ts

const sig = req.headers.get("stripe-signature");
const rawBody = await req.text();

const event = stripe.webhooks.constructEvent(
  rawBody,
  sig,
  webhookSecret,
);

Unlock RPC呼び出し

目的: 残高確認、台帳追加、Unlock作成をDB側のRPCへ寄せる。防いでいる問題: API途中失敗による残高と解錠状態の不一致。検証: Route HandlerとmigrationのRPC定義を確認。参照元: app/api/open-post/route.ts

const { data, error } = await admin.rpc("open_content_with_wallet", {
  p_user_id: userId,
  p_content_id: postId,
  p_coin_cost: coinCost,
});

購入モーダルE2E

目的: PCとスマートフォン幅で購入モーダルの表示と主要CTAを確認する。防いでいる問題: 決済導線に入る前のUI崩れ。検証: Playwrightで複数viewportを確認。参照元: tests-e2e/purchase-modal.spec.ts

await expect(dialog).toBeVisible();
await expect(dialog.locator("h2").first()).toHaveText(scenario.heading);

const box = await dialog.boundingBox();
expect(box?.width).toBeLessThanOrEqual(viewport.width + 1);

AI/Codexの利用方法

開発支援として使う

要件・仕様・優先順位はYuta Matsufujiが決め、Codex/OpenAIは調査、実装、レビュー支援として使います。受入判断は人間側で行い、型チェック、テスト、ビルド、画面確認、コードレビューで確認してから採用します。

担当範囲と検証範囲

透明性

本ページは、実装コード、テスト、設計資料から確認できる内容を掲載しています。利用者数や売上等の事業成果ではなく、設計・実装・検証内容をケーススタディとして紹介しています。