テーマ
Research & Design Decisions — voting
Purpose: voting spec の design 書き直し(data-model の 3 系統契約と Stripe Webhook 駆動アーキテクチャへの適合)で行った調査・統合判断・トレードオフを記録する。
Summary
- Feature:
voting - Discovery Scope: Extension(既存の有料投票フロー設計を 3 系統データアーキテクチャと最新の data-model 契約に合わせて再構築)
- Key Findings:
- CreditPackage は microCMS 所有(完全イミュータブル + isActive のみ可変)。voting spec 内に重複の
creditPackageServiceを持たず、features/cms/services/creditPackageServiceを call する。 - 投票期間は
lib/voting-period.getVotingPeriod()から取得し、Server Action 側でガードする。Vercel ENV の値変更は再デプロイで反映。 - 合計票数 / 合計金額は クライアント値を信用せず Server で再計算(
createPaymentIntent)、かつ Webhook 受信時にも再取得して再計算する。CreditPackage が完全イミュータブルなので両時点で同一値が得られる。 - Vote 一括生成は Webhook ハンドラの
prisma.$transactionに集約。Purchase.status='PROCESSING' 時のみ遷移する FOR UPDATE 行ロックで冪等化。
- CreditPackage は microCMS 所有(完全イミュータブル + isActive のみ可変)。voting spec 内に重複の
Research Log
Topic 1: data-model 契約の変化と voting への波及
- Context: 旧 voting design は Prisma 単独前提で
features/voting/services/{creditPackageService,votingPeriodService,nicknameService}.tsを抱え、Purchase にtotal*、PurchaseItem に*Snapshot、Vote にstatus列があった。 - Sources Consulted:
voting/design.md(旧)、data-model/design.md(新)、steering/tech.md - Findings:
- CreditPackage は microCMS 所有 →
features/cms/services/creditPackageServiceを call。PrismaCreditPackage型は存在しない。 - VotingPeriod は ENV →
lib/voting-period.getVotingPeriod()。null フォールバックではなく throw。 - Vote イミュータブル →
status列なし、有効性は Purchase.status との JOIN。 - PurchaseItem snapshot 列なし → 価格は microCMS から都度取得。
- CreditPackage は microCMS 所有 →
- Implications:
- voting spec の File Structure Plan から
services/配下のサービスファイルを削除(features/voting/services/自体はnicknameServiceのために残るが、所有は data-model)。 - 合計金額の改竄防止は「Server で都度再計算」のみ。Snapshot 列に頼らない。
- voting spec の File Structure Plan から
Topic 2: Webhook 駆動の単一権威化と冪等化
- Context: 「決済 = 投票」のワンショットを保証するには、Vote 生成のトリガーを 1 箇所に絞る必要がある。
- Sources Consulted: Stripe Webhook 公式ドキュメント、
data-modelのPaymentStatusenum 設計 - Findings:
- クライアントから Vote 生成を呼ばない方針が要件 1.1(単一フロー)と非機能-冪等性に整合。
- Webhook 重複配信(Stripe 公式仕様で複数回送られる可能性あり)に対し、
paymentIntentIdUNIQUE + status='PROCESSING' 限定で UPDATE することで冪等。 - Stripe Webhook は 200 を返さなければ自動再試行されるため、microCMS 一時障害時もリカバリ可能。
- Implications:
- クライアントは
getPurchaseStatuspolling で SUCCEEDED 観測後に UI 反映するだけ。 - Webhook ハンドラに
prisma.$transaction+ FOR UPDATE を明示。
- クライアントは
Topic 3: クライアント polling の打ち切り時間
- Context: Webhook 配送遅延(まれに数秒〜数十秒)時の UX を決める必要があった。
- Sources Consulted: 既存 voting design、Stripe SLA
- Findings:
- 30 秒以内に SUCCEEDED 観測されない場合、
pending_settlement表示で「後ほど反映」を案内するのが典型的な UX。 - 5 分以内に未観測なら error にしてサポート連絡導線を出す(
ui-design.md委譲)。
- 30 秒以内に SUCCEEDED 観測されない場合、
- Implications:
usePurchaseFlowの状態機械にpending_settlementを含める。- 1 秒間隔で polling、
visibilitychangeで resume。
Topic 4: Stripe Payment Element の選択(Link 優先)
- Context: 要件 7.x で「ワンクリック決済を優先、未登録ユーザーはカード入力」を要求。
- Sources Consulted: Stripe Payment Element / Link 公式ドキュメント
- Findings:
automatic_payment_methods: { enabled: true }で PaymentIntent を作成すると、Stripe が利用環境に応じて最適な決済手段(Link / カード / Apple Pay / Google Pay 等)を自動表示する。- Link は Stripe Dashboard で有効化すれば追加実装不要。
- Implications:
- 個別の決済手段判定は不要。
<PaymentElement>をそのまま使う。 - 設計を簡潔に保てる。
- 個別の決済手段判定は不要。
Architecture Pattern Evaluation
| Option | Description | Strengths | Risks / Limitations | Notes |
|---|---|---|---|---|
| クライアント主導: Stripe 成功 callback で Vote 生成 | Server Action 経由で Vote を作成 | レスポンスが早い | 冪等化が難しい、ユーザーが画面を閉じると失敗 | 不採用 |
| 採用: Webhook 駆動の単一権威 | Stripe Webhook で Vote 生成 | 冪等・サーバー権威 | クライアントは polling 必要 | 要件 1.1 に整合 |
| Server Action + Webhook 二重生成防止 | Both で生成し UNIQUE で抑止 | 速度と信頼性両立 | 実装複雑度高、冪等化が二重に必要 | 不採用 |
| Snapshot 列を使う旧設計 | 価格を PurchaseItem に保存 | 過去明細が自己完結 | data-model の Out-of-scope | 不採用 |
Design Decisions
Decision: 合計票数を Webhook 受信時に microCMS から再取得して再計算する
- Context: PurchaseItem に
*Snapshot列を持たない方針(data-model)で、決済成功時の合計票数をどう確定するか。 - Alternatives Considered:
- Stripe PaymentIntent の metadata に totalCredits を保存し、Webhook 時に使う。
- Webhook ハンドラが microCMS から都度取得して再計算する。
- Selected Approach: 2。CreditPackage が完全イミュータブル(
data-modelReq 3.3)であることを根拠に、再取得しても値が変わらない前提で動作する。 - Rationale: metadata は 500 文字制限がありアイテム数が多い場合に収まらない。microCMS 取得は数 ms オーダーで Webhook タイムアウト内に収まる。
- Trade-offs: microCMS が一時的にダウンすると Webhook も失敗するが、Stripe が自動再配信するため最終的には成功する。
- Follow-up: Webhook ハンドラに「microCMS 取得失敗時は 500 を返して再配信を促す」分岐を実装。
Decision: クライアントは Vote を生成しない(楽観更新のみ)
- Context: 旧設計の一部に「クライアントから Vote 作成 Server Action を呼ぶ」案があったが、Webhook と二重で生成する複雑さがあった。
- Alternatives Considered:
- クライアントが Server Action で Vote を作成し、Webhook は status 更新のみ。
- Webhook のみが Vote を作成し、クライアントは status polling と楽観更新。
- Selected Approach: 2。Webhook 駆動の単一権威。
- Rationale: 冪等化が圧倒的に簡単。Stripe Webhook は外部からの破壊不可な権威として扱える。
- Trade-offs: ユーザー観測は polling 経由で多少遅延するが、UX 上問題ない範囲。
- Follow-up: クライアント側で楽観的得票数加算を行うフックを
home/candidate-detailで活用。
Decision: useNickname の正規化はクライアント・サーバー両方で行う
- Context: 要件 6.3 は「未入力・空白のみは無効」とし、UX として即時フィードバックが必要。
- Alternatives Considered:
- サーバー側のみで検証し、クライアントはサブミット時に結果を受け取る。
- クライアント側でも
normalizeNicknameを実行し、リアルタイムバリデーション。
- Selected Approach: 2。ただし「真の検証」はサーバー側で再実施。
- Rationale: UX 上、入力エラーを即時表示するのが望ましい。クライアント検証は spoof 可能だがサーバーで再検証するため害なし。
- Trade-offs: クライアントとサーバーで同じ正規化関数を保持(
data-model所有を両方から import)。 - Follow-up: 正規化関数のテストは data-model 側で行い、voting spec は使用箇所のテストのみ。
Risks & Mitigations
- Webhook 配送遅延: 1 秒間隔 polling +
pending_settlement表示。5 分以内未観測でサポート連絡導線。 - Stripe Dashboard 設定漏れ(Webhook URL / Link 有効化): 環境構築チェックリスト(steering/structure.md の運用ノートに後続追加)。
- microCMS のレート制限:
unstable_cacheでgetActiveCreditPackagesを 60-180 秒キャッシュ。Webhook 時はgetCreditPackage(id)を直接(キャッシュ不要)。 - クライアント時計の改竄による期間判定回避: Server Action 側で必ず
getVotingPeriod()で再判定。 - 大量 Vote 生成のトランザクション長期化: 合計票数を運用上の上限内(数千)に制限。CreditPackage の最大票数 × 最大数量で実質上限を設計時に確認。
References
.kiro/specs/voting/requirements.md— 機能要件.kiro/specs/data-model/design.md— Vote / Purchase / PurchaseItem / CreditPackage / nicknameService / voting-period.kiro/specs/home/design.md— 投票導線の起点- Stripe Payment Intents API
- Stripe Webhooks 署名検証
- Stripe Link
- Prisma
$transaction
Gap Analysis: voting × 既存コードベース(2026-05-17 時点)
目的: 既に approved 済みの
votingspec(requirements / design / tasks)と、2026-05 の大規模リセット直後の現コードベースとの間に残るギャップを洗い出し、/kiro-impl voting起動前に押さえるべき前提・選択肢・リスクを整理する。本ファイルは決定ではなく情報提供。
0. Discovery Scope
- Discovery Scope: Greenfield 寄りの Extension(spec は完成済み・コードはリセット後の最小骨格のみ)
- 対象 spec:
voting(spec.json.phase = "tasks-generated"、approvals: requirements/design/tasks すべて approved) - 前提:
data-modelspec の tasks 完了を前提に置く設計(本 spec の冒頭注記による)
1. Current State Investigation
1.1 既存実装(投票 / 決済 / 関連基盤)
調査結果: 投票・決済に該当する実装は一切存在しない。リセット後のスケルトンのみ。
| 観点 | 期待する実体 | 現コード | 状態 |
|---|---|---|---|
Server Action: createPaymentIntent | app/actions/purchase.ts | app/actions/ ディレクトリ自体が存在しない | Missing |
Server Action: getPurchaseStatus | 同上 | 同上 | Missing |
| Stripe Webhook ハンドラ | app/api/webhooks/stripe/route.ts | app/api/ ディレクトリが存在しない | Missing |
features/voting/hooks/usePurchaseFlow 等 | features/voting/hooks/* | features/ ディレクトリ自体が存在しない | Missing |
features/voting/components/* | PurchaseFlowContainer / PurchaseCreditsModal / StripePaymentModal / NicknameInput / CreditPackageRow | 同上 | Missing |
features/cms/services/creditPackageService | data-model spec で提供される予定 | 未実装(data-model の tasks 未着手) | Upstream Missing |
features/cms/services/candidateService.getCandidate | data-model spec | 未実装 | Upstream Missing |
features/voting/services/nicknameService.normalizeNickname | data-model spec(features/voting/services/ の所有は data-model) | 未実装 | Upstream Missing |
lib/voting-period.getVotingPeriod | data-model spec | 未実装(lib/ には utils.ts のみ) | Upstream Missing |
lib/prisma.ts | data-model spec | 未実装 | Upstream Missing |
prisma/schema.prisma(Purchase / PurchaseItem / Vote / PaymentStatus enum) | data-model spec | prisma/ ディレクトリ自体が存在しない | Upstream Missing |
| Stripe SDK | stripe / @stripe/stripe-js / @stripe/react-stripe-js | stripe@^22 と @stripe/stripe-js@^9 は package.json に既導入。@stripe/react-stripe-js は 未導入 | Partial |
| Zod | Server Action 入力検証 | zod@^4 導入済み | Present |
| React Hook Form | フォーム制御 | react-hook-form@^7 / @hookform/resolvers@^5 導入済み | Present |
| Sonner(トースト) | エラー通知の選択肢 | sonner@^2 導入済み | Present(任意) |
app/page.tsx | 候補者一覧 → 投票導線 | プレースホルダのみ(再構築中 メッセージ) | Stub |
app/providers.tsx | Provider 集約 | 空の pass-through | Stub |
auth.ts / app/admin/** | 保有しない(steering 方針) | 存在しない | Correctly Absent |
1.2 関連 spec の状況
| spec | phase | approval 状況 | 本 spec への影響 |
|---|---|---|---|
data-model | tasks-generated / approved | 完了 | 本 spec は data-model の services を call する前提。data-model の /kiro-impl が先行している必要がある |
home | tasks-generated / approved | 完了 | <PurchaseFlowContainer> を import するため、本 spec のコンポーネントが home の実装より先に存在する必要がある(または並行) |
candidate-detail | tasks-generated / approved | 完了 | 同上。useVoteFlow / useVotingPeriodStatus を import |
candidate-ranking | (未確認だが存在) | — | 直接の依存なし(本 spec は ranking の純関数を借りない) |
1.3 環境・外部統合の準備度
| 外部 | 必要なもの | 現状 |
|---|---|---|
| Stripe Dashboard | API キー(test/live)、Webhook エンドポイント設定、Link 有効化、対応決済手段の有効化 | 未設定(運営オペ作業)。STRIPE_SECRET_KEY / STRIPE_WEBHOOK_SECRET / NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY の投入が必要 |
| microCMS | creditPackages / candidates コレクション + 読み取り専用 API キー | data-model spec のタスクで運営オペが必要(本 spec 外) |
| Neon / Postgres | DATABASE_URL、Purchase/PurchaseItem/Vote テーブル | ローカルは docker-compose.yml が既にある(postgres:17 想定)。Prisma マイグレーションは data-model spec タスク |
| Vercel ENV | VOTING_PERIOD_START / VOTING_PERIOD_END | 未投入(運営オペ) |
@stripe/react-stripe-js | クライアント Payment Element | package.json に未追加。本 spec の tasks 1.1 で追加予定 |
| Stripe CLI | Webhook ローカル受信(stripe listen) | ローカル動作確認に必須(docs にメモを残すべき) |
1.4 保護パスと境界
/kiro-impl voting で新規生成すべきファイルはすべて CLAUDE.md の保護パス配下(features/**, app/api/**, app/actions/** 相当, app/_components/** は対象外だが本 spec は直接使わない)。直接 Edit / Write を回避し、必ず /kiro-impl voting 経由で生成する。
例外的に編集可能なのは:
package.json(Stripe / React Stripe.js の依存追加)next.config.ts(CSP やリモートパターン追加が必要なら)- ローカル
.env.localテンプレ整備
2. Requirements Feasibility Analysis(Requirement-to-Asset Map)
| Req(代表) | 必要アセット | 現状 | Gap タグ |
|---|---|---|---|
| F1.1 単一フロー(候補者+パッケージ+ニックネーム+決済) | <PurchaseFlowContainer> + usePurchaseFlow | Missing | Missing(本 spec で新規作成) |
| F1.2 決済成功 = 合計票数加算 | Webhook handleSucceeded + vote.createMany | Missing | Missing(本 spec) |
| F1.3 失敗時 Vote 非生成 | Webhook handleFailed | Missing | Missing(本 spec) |
| F1.5 連続購入抑止 | usePurchaseFlow の step !== 'select' ガード | Missing | Missing(本 spec) |
| F1.6 期間外サーバーガード | createPaymentIntent 内 getVotingPeriod() | Missing(lib/voting-period 自体が upstream missing) | Blocked by data-model |
| F2.x モーダル開閉・選択リセット・処理中無効化 | useVoteFlow + <PurchaseFlowContainer> | Missing | Missing(本 spec) |
| F3.x モーダル内コンテンツ | <PurchaseCreditsModal> | Missing | Missing(本 spec) |
| F4.x 有効パッケージを displayOrder 順で取得 | creditPackageService.getActiveCreditPackages | Missing | Blocked by data-model |
| F5.x 数量入力 + 合計即時更新 + 0 で disable | usePurchaseCart + <CreditPackageRow> | Missing | Missing(本 spec) |
| F5.5 改竄抑止のサーバー再計算 | createPaymentIntent 内で getCreditPackage(id) 並列取得 | Missing | Missing(本 spec、ただし upstream getCreditPackage 必須) |
| F6.x ニックネーム入力 + localStorage 永続化 + 1〜32 + 補助テキスト | useNickname + <NicknameInput> + normalizeNickname(data-model) | Missing | Missing(本 spec) + normalizeNickname は Blocked by data-model |
| F7.x Stripe Payment Element(Link 優先 + カード) | <StripePaymentModal> + @stripe/react-stripe-js | Missing(SDK も未導入) | Missing(本 spec) |
| F8.x 決済金額・対象表示・第三者プロバイダ告知 | <StripePaymentModal> UI | Missing | Missing(本 spec) |
| F9.x 決済処理中状態 + 成功フィードバック | usePurchaseFlow step 機械 | Missing | Missing(本 spec) |
| F10.x 成功後シーケンス(加算 + 永続化 + Confetti + クローズ + リセット) | usePurchaseFlow.onSuccess + useVoteFlow.handlePaidVoteConfirmed + useNickname.persist | Missing | Missing(本 spec) |
| F11.x 閉じ時リセット + 処理中閉じ不可 | <PurchaseFlowContainer> | Missing | Missing(本 spec) |
| 非機能-トランザクション | Webhook 内 prisma.$transaction | Missing | Missing(本 spec、要 Prisma スキーマ) |
| 非機能-冪等性 | paymentIntentId UNIQUE + status='PROCESSING' FOR UPDATE | Missing | Blocked by data-model(UNIQUE 制約) + 本 spec(Webhook ロジック) |
Unknown / Constraint / Complexity
- Unknown V1:
data-modelspec の/kiro-implが完了しているか不明。本 spec の/kiro-impl voting起動前にkiro-spec-status data-modelで確認すべき。 - Unknown V2: Stripe Dashboard の Webhook URL は Vercel デプロイ後に確定する。ローカルでは
stripe listen --forward-to localhost:3000/api/webhooks/stripeを必須として開発ドキュメントに記述する必要がある。 - Unknown V3:
@stripe/react-stripe-jsのバージョンは未確定。@stripe/stripe-js@^9と互換のメジャーを選ぶ必要がある(現状 v3.x 系がstripe-js@^9と同期、要確認)。 - Unknown V4: CSP ヘッダーで Stripe.js を許可する設定が
next.config.tsに未投入。本番運用に向けてscript-srcへのhttps://js.stripe.com追加検討が必要(本番デプロイ前に追加)。 - Constraint V1: 保護パス(
app/api/**,features/**,app/actions/相当)はすべて/kiro-impl voting経由で生成する。直接Edit/Write禁止。 - Constraint V2:
useNicknameの クライアント側 からnormalizeNicknameを import する設計。data-model spec が「Server / Client 両用の純関数として export」することを保証しているため、'use client'を含まない純関数ファイル(features/voting/services/nicknameService.ts)に置く必要がある(本 spec design の Boundary Commitments 通り)。 - Complexity Signal: アルゴリズム的に難所はない。難所は (i) Webhook 冪等性の二段ガード(FOR UPDATE + 条件付き updateMany)、(ii) クライアント polling + visibilitychange + 30s/5min タイムアウトの 3 段階遷移、(iii) Stripe Payment Element の
clientSecretライフサイクル(モーダル閉じ時の Elements インスタンス破棄)、(iv)useNicknameの SSR 安全な初期化。
3. Implementation Approach Options
Option A — design / tasks をそのまま実装(Stay-the-course)
- 概要: approved 済みの design.md / tasks.md(セクション 1〜7、サブタスク 1.1 / 1.2 / 2.1 / 3.1 / 3.2 / 4.1 / 4.2 / 4.3 / 5.1〜5.5 / 6.1〜6.5 / 7.1〜7.3)を
/kiro-impl votingで順番に実装する。前提として/kiro-impl data-modelを先行完了させる。 - トレードオフ:
- 良: spec / steering / Boundary Commitments と完全整合。再設計コストゼロ。Webhook 駆動の単一権威 + 冪等化が design に明文化済み。
- 良: 依存追加は
@stripe/react-stripe-jsのみで済む(他は既導入)。 - 良: タスクの粒度が細かく、reviewer subagent が境界違反を検出しやすい。
- 悪:
data-modelの/kiro-impl未完了時に着手すると upstream missing で半数以上のタスクが blocking になる。 - 悪: テスト基盤(Vitest / Playwright)が未導入のため、tasks 7.1〜7.3 は「テストファイルを書くだけで CI 実行できない」状態になる可能性がある(
data-modelresearch の R2 と同じ懸念)。
- 適合度: 最も高い。Discovery Scope(Extension)と Boundary Commitments の整合性は完璧。
Option B — data-model と並行で進め、契約モックで stub する(Aggressive Parallelization)
- 概要:
data-modelの services を 未実装のまま モック関数(/* TODO: data-model 実装後に差し替え */コメント付き)で stub し、votingの UI / Hook / Server Action 骨格を先に走らせる。data-model完了後にモックを実 SDK 呼び出しに置換する。 - トレードオフ:
- 良: 開発時間が圧縮され、フロントエンド作業者とデータ層作業者が並行できる。
- 良: UI レイアウト(
<PurchaseCreditsModal>/<StripePaymentModal>)を早期に視覚化できる。 - 悪: モックと実 SDK のシグネチャ齟齬で 再修正コストが発生しやすい(特に Prisma の Purchase レコード形状や microCMS の
getCreditPackage戻り値型)。 - 悪:
/kiro-impl votingのレビューア subagent は「実 import が存在しない」状態を完了として認めない可能性が高い。 - 悪: Boundary Commitments の "Allowed Dependencies" に
data-modelservices が含まれており、stub 化は境界違反に近い。
- 適合度: 低。
data-modelの所要工数が小さい(M)ので、並列化のメリットがコストを上回りにくい。
Option C — 最小実装で先に E2E 通し、Webhook 周辺は後追い(Vertical Slice First)
- 概要: tasks のうち F1.1 / F1.5 / F5.x / F6.x / F7.x / F9.x の単純な happy path のみ(3.1, 4.1, 4.2, 5.1〜5.5, 6.1〜6.5)を最小実装して 1 候補者 1 パッケージで決済完了するまで通し、F1.3(失敗) / F1.6(期間外) / 非機能-冪等性 / Webhook 重複 / 払戻 / polling タイムアウトは後続イテレーションに回す。
- トレードオフ:
- 良: 最短で「投票が成立する」状態を見せられる。Stripe 設定の生死を早期に検証可能。
- 悪: 非機能-冪等性 は本来 Webhook 実装と同時に必要(Stripe は実環境で当然のように重複配信する)。後追いだと初回本番投入時の事故リスクが高い。
- 悪: tasks.md の section/サブタスクのつながり(
_Depends:関係)を崩すため、レビューア subagent の検出がノイジーになる。
- 適合度: 中。デモ用なら有効だが、本番投入を見据えるなら Option A 推奨。
4. Effort & Risk
| 観点 | 判定 | 1 行根拠 |
|---|---|---|
Effort(本 spec 単独、data-model 完了後) | L(5〜10 日) | 新規ファイル数が多い(Server Action 1、Webhook 1、Hook 5、Component 5、型 1)。Stripe ローカルテスト + Webhook 冪等性検証 + クライアント polling の 3 セットで統合確認のラウンドが膨らむ。 |
Effort(data-model 含む合計) | XL(8〜17 日) | data-model M + voting L。並列化は限定的なため線形に近い。 |
| Risk | Medium-High | (1) Stripe Webhook 冪等性の検証は本番相当の負荷でしか露見しないバグが残りやすい、(2) useNickname の SSR / Hydration 安全性は React 19 + Next.js 16 で挙動を確認する必要、(3) @stripe/react-stripe-js のバージョン互換、(4) vote.createMany で合計票数が数千を超えるシナリオの実測未経験。アーキテクチャ的な未知数は小さいが、統合点が多い。 |
5. design phase / 実装直前への推奨事項
design.md / tasks.md は approved 済み。
/kiro-impl votingを起動する前にチェックしておくべき項目を列挙する。
- 推奨アプローチ: Option A(approved 通りに実装)。並列化や最小実装で時間を節約するメリットより、境界遵守 + 冪等性確保のメリットが大きい。
- 着手順序の明確化:
kiro-spec-status data-modelを実行し、data-modelの tasks が completed であることを確認してから/kiro-impl votingを起動する。完了していない場合は先に/kiro-impl data-modelを起動。 - 依存追加のレビュー:
package.jsonに@stripe/react-stripe-jsを追加(tasks 1.1)。互換バージョンは@stripe/stripe-js@^9と整合するメジャーを採用(本 spec design / tasks の中ではバージョン固定なし、/kiro-impl中に最新の互換版を確認)。stripe@^22、@stripe/stripe-js@^9は既にpackage.jsonに存在するので追加不要。
- 環境変数テンプレ:
.env.exampleに以下を追加する手順を/kiro-impl実行時のチェックリストに含める(/kiro-impl中に作成可、.env.exampleは spec 外パスのため直接編集可)。STRIPE_SECRET_KEYNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYSTRIPE_WEBHOOK_SECRETUPSTASH_REDIS_REST_URL/UPSTASH_REDIS_REST_TOKEN(steering で予定されているが本 spec の tasks には未含。レート制限は steering 方針のため後続 spec で対応する想定)
- 本 spec の所有境界(再確認):
useNickname/usePurchaseCart/usePurchaseFlow/useVoteFlow/useVotingPeriodStatusを 本 spec 単独所有(features/voting/hooks/)。home/candidate-detailは import のみ。normalizeNicknameの 再定義禁止(features/voting/services/nicknameService.tsは data-model 所有)。lib/voting-period.tsの 複製禁止(data-model 所有)。
- Research Needed(次フェーズに持ち越し):
- R1:
@stripe/react-stripe-jsの最新互換版を確認(stripe-js@^9系との Peer Dependency)。 - R2: Next.js 16 の Server Action における CSRF 既定動作の現行仕様確認(design の Security Considerations 前提の有効性)。
- R3: Vitest / Playwright 導入は本 spec の tasks 7.1〜7.3 で必要だが、テスト基盤 spec の有無を
kiro-spec-statusで確認。なければtesting-foundation新 spec を立てるか、tasks 7.x を「テストファイル作成のみ・CI 連携は別 spec」と注記して進める判断が必要。 - R4: 本番運用での Stripe Webhook URL(Vercel 環境変数経由ではなく Stripe Dashboard 側設定)の運用フローを
steering/structure.mdかdocs-site/**に追記。 - R5:
vote.createManyの最大件数想定(CreditPackageの最大票数 × 最大数量 × カートアイテム数)を運用上限として明示し、design の Performance に追記。例: 最大票数 150 × 数量 20 × アイテム 20 = 60,000 件は要再評価(PostgresINSERT一発の安全上限を超える可能性)。
- R1:
- Fallback シナリオ:
- Stripe Link が日本市場で利用不可だった場合:
automatic_payment_methods設定は維持しつつ Link を Dashboard で無効化。requirements の「ワンクリック決済優先」表現は維持(他のウォレットが代替する)。 - Webhook が安定動作しない場合(極めて稀): クライアント polling 経由でも Server Action から
stripe.paymentIntents.retrieve(id)を呼び、Webhook 未着でもSUCCEEDEDが観測できる二重経路を追加(design に未記載の補強策)。
- Stripe Link が日本市場で利用不可だった場合:
6. Output Checklist
- [x] Requirement-to-Asset Map(セクション 2 表)
- [x] Implementation Options A / B / C(セクション 3)
- [x] Effort(L / XL)& Risk(Medium-High)判定(セクション 4)
- [x] 推奨事項(Option A)と Key Decisions の再確認(セクション 5)
- [x] Research Needed(R1〜R5)
- [x] Fallback シナリオ(セクション 5.7)
7. 次の一手(design phase に進むうえで)
本 spec は既に
designapproved だが、本 gap analysis を踏まえた次の一手を整理する。
kiro-spec-status data-modelを実行し、data-modelの tasks 進捗を確認する。未完なら 先に/kiro-impl data-modelを起動(本 spec の前提条件)。data-model完了後に/kiro-impl votingを起動。サブタスク順は tasks.md の依存関係(_Depends:)に従う(1.1 → 2.1 → 3.1 → 3.2 → 4.1 → 4.2 → 4.3 → 5.x → 6.x → 7.x)。- Storybook / Vitest / Playwright の導入有無を別途確認し、なければ
/kiro-spec-init testing-foundationで新 spec を立てる(本 spec の tasks 7.x の CI 検証が成立する前提を整える)。 - Stripe Dashboard の Webhook エンドポイント設定 + Link 有効化 を運営オペとして実施(
/kiro-implの前にチェックリストとして残す)。 @stripe/react-stripe-jsのバージョンを/kiro-impl voting内の tasks 1.1 で確定してpackage.jsonに追加する。- 想定外の挙動(冪等性で Vote が二重生成、polling で SUCCEEDED 観測できないなど)が発生した場合、本 research.md の Risks & Mitigations セクションを参照してデバッグ方針を決める。