Skip to content

feat(api): Stripe(価格・商品・請求・Checkout)を oRPC 契約ファーストで公開する - #38

Merged
reiroop merged 32 commits into
mainfrom
feat/prices-orpc-contract
Aug 4, 2026
Merged

feat(api): Stripe(価格・商品・請求・Checkout)を oRPC 契約ファーストで公開する#38
reiroop merged 32 commits into
mainfrom
feat/prices-orpc-contract

Conversation

@reiroop

@reiroop reiroop commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

概要

Stripe を oRPC の契約ファースト API として公開する層を追加する。価格・商品・請求・Checkout の
4 リソースを、Nuxt の Nitro サーバーにマウントした oRPC ルーターとして実装する。契約
(packages/api-contract)・実装(packages/api)・ホスト(apps/web/server)の 3 層に分け、
クライアントはサーバー実装ではなく契約にリンクする。あわせて、各設計判断の理由を
openspec/project.md の「規約」に文書化する。

契約ファーストなので、api-contract(契約)→ api(ハンドラ・変換層)→ apps/web/server
(ホスト・起動時検証)の順に読むと差分を追いやすい。

設計方針(理由の詳細は openspec/project.md の「規約」)

  • 出力は allowlist: 公開 View を自作 z.object で明示列挙し、Stripe オブジェクトを透過しない。
    PII・内部フラグ・将来 Stripe が増やすフィールドを漏らさない。展開参照(customer/product 等)は
    ID に正規化する。
  • 入力は選択的透過: 対応するパラメータだけを受け、expand は受け付けない(出力 allowlist を
    迂回してネストした metadata を漏らす経路になるため)。
  • 命名は camelCase: 消費側(TypeScript / Vue)に揃える。Stripe の snake_case はハンドラの
    変換層で対応づける。
  • 一覧は前方カーソルのみ: レスポンス形状は { data, nextCursor }。逆方向カーソル
    ending_before は公開しない(カーソル導出戦略を、契約を変えずに差し替えられるようにするため)。
  • traq_id は出力しない: ユーザー同定は自前 DB を単一ソースとし、Stripe metadata に持たせない
    (セッション・管理者依存エンドポイントの契約とバックエンドを実装する #18)。
  • 変更系は既定で無効化: 全プロシージャが無認証の現状で、決済系の書き込み(Invoice の確定・
    価格や商品の変更)を塞ぐ(mutationsEnabled / assertMutationsEnabled)。本来の認可は 認可・レート制限を導入し、無認証アクセスを塞ぐ #15
  • 起動・実行時の堅牢化: 起動時に STRIPE_SECRET_KEY の存在を検証する(本番のみ、Stripe シークレットの存在を起動時に検証し、設定漏れで起動を失敗させる #23)。
    Stripe の apiVersion を固定し、多段の請求フローは派生冪等キーでリトライ安全にする。

現状の Stripe 系エンドポイントは Stripe を薄く包む管理・仲介層で、会員向けのドメイン API では
ない。会員向けはドメイン言語(会費・支払い状況)の別レイヤを設け、Stripe を裏に隠す(#27/#28)。

この PR で対応する issue

関連・フォローアップ

テスト

  • 34 件(prices / products / invoices / checkout / views)。allowlist の除外・展開参照の ID 正規化・
    冪等キーの派生・カーソル導出・変更系ガードを検証する。
  • pnpm typecheck / lint / knip / test / build すべてグリーン。

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 新機能

    • Stripe の商品・価格・請求書・Checkout セッションを取得・一覧表示する API を追加。
    • 商品と価格の更新、請求書の発行に対応。
    • 一覧取得でフィルターとカーソルページネーションを利用可能に。
    • 公開データを必要な項目に限定し、個人情報や内部情報の露出を防止。
  • 設定

    • 本番環境で Stripe の設定漏れを起動時に検知。
    • 更新操作を有効・無効に切り替え可能。
  • 変更

    • ホームページを静的表示に変更し、ヘルスチェック表示を削除。
    • 開発手順と API 設計ドキュメントを更新。

reiroop and others added 30 commits August 2, 2026 23:15
Stripe をラップした prices エンドポイントを契約ファーストで追加し、以後の
Stripe 由来 API の雛形とする2パターンを確立する。

- retrieve: 単一の Price を返す。
- list: エンベロープ(has_more/data)は自前 zod で構造検証し、各要素は Stripe
  ベースの派生型 PriceView。product_id で Stripe の product フィルタに写す。

出力は両者とも PriceView。Stripe の metadata は Checkin が使う traq_id だけに
絞り、内部キーを晒さない。この絞り込みは型では強制できない(Stripe.Metadata の
インデックスシグネチャが構造的に代入可能なため)ので、変換を toPriceView() の
1箇所に閉じ込める。top-level の Stripe 標準フィールドはそのまま公開するため、
必要な項目が増えても契約を変えずに済む。Stripe SDK の lastResponse は非列挙
プロパティのためシリアライズされず漏れない。

Stripe クライアントは db と同様にリクエストコンテキスト経由で遅延注入する。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
consistent-type-assertions を assertionStyle:'never' で有効化し、型チェッカーを
迂回して実際とズレた型を黙って通す `as` を禁止する(`as const` と名前空間 import
`* as` は対象外)。テストのスタブなど安全が確認できる箇所は、なぜ安全かを添えた
eslint-disable で個別に許可する。既存スタブは Context / Stripe.Price への最小
キャストに集約した。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
一覧に limit / starting_after を選択的透過で追加し、フロントが Stripe の
カーソルページングを使えるようにする。支援する入力だけを受け、expand 等の
その他 Stripe パラメータは受け付けない(expand はネストした product.metadata
等の漏洩経路になるため)。zod の入力スキーマが未知キーを除去するので、この
制限は検証層で強制される。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- コメントの「支援するパラメータ」を「対応するパラメータ」に修正(不自然な直訳)
- eslint コメントから紛らわしい `import * as` の言及を削除し、`as const` のみが
  本ルールの例外である旨に是正
- list ハンドラのコメントに、フロントが data 末尾の Price ID を次回の
  starting_after に使う導出手順を明記
- product_id とページネーションを併用する経路の転送テストを追加

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stripe をラップする管理系エンドポイント群を契約ファーストで実装する。
セッション不要で Stripe SDK だけで完結する範囲(カタログ/請求の管理面)を対象とし、
会員フロー(認証・セッション・webhook 依存)は #16 #17 #18 に切り出した。

追加した契約とバックエンド:
- products: list / update
- invoices: list / create(Invoice 作成 → 価格を項目化 → 確定して支払い URL を返す)
- checkout: listSessions
- prices: update を追加、list に active/type/ending_before フィルタを拡充

共通化:
- 出力の metadata 絞り込みを汎用の narrowMetadata<T> と型 WithTraqId<T> に一般化し、
  全リソースで変換点を1箇所に集約。
- ページネーション・書き込み可能 metadata の入力を params.ts に共通化。
- 入力は選択的透過(フィルタ + ページネーション、expand 不可、mutation の metadata は
  traq_id のみ)。

テスト: 各リソースの Stripe パラメータ写像・metadata 絞り込み、invoices.create の
多段フローと支払い URL 欠如時のエラーを検証(テスト共有ヘルパ test-utils を追加)。

認可は未実装のまま(#15)。本 PR の管理系エンドポイントは認可導入時に保護対象へ含める。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
セキュリティ:
- invoices.create を collection_method: send_invoice に固定(days_until_due:30)。
  既定の charge_automatically は finalize 時に顧客の既定支払い方法へ自動課金され得るが、
  このエンドポイントは支払い URL を返すリンク払いを意図しているため。

型・テスト:
- stripeFixture を overrides: Partial<T> にし、呼び出し側でトップレベルのフィールド名
  タイポをコンパイル時に検出できるようにした(no-unnecessary-type-parameters の
  disable も不要化)。
- テスト追加: invoices.create の metadata 未指定ブランチ、invoices.list の
  subscription_id 写像、prices/products の update の refine(最低1フィールド)負テスト。

コメント:
- views.ts の設計根拠を narrowMetadata 側に集約し、ネストした metadata
  (Invoice.lines[].metadata 等)が expand せずとも既定レスポンスに含まれ透過する点を
  正確化。metadata 以外の PII の公開範囲と併せて #15 で扱う旨を明記。
- 「選択的透過」の定義と expand 除外の原則を params.ts に集約。
- 「支払い URL」の用語を統一。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
機微リソース(Invoice / CheckoutSession)の出力を、Stripe オブジェクトの透過から
公開フィールドを明示列挙する allowlist へ切り替える。透過 + metadata 絞り込みは、
metadata 以外の PII(customer_email / customer_name / 住所 等)やネストした metadata
(lines[].metadata 等)を既定で素通ししていた。allowlist は既定で安全(列挙外は出ない)で、
将来 Stripe が追加するフィールドも自動では露出しない。

- 契約: InvoiceView / CheckoutSessionView を本物の z.object で定義。customer /
  payment_intent は ID のみ、metadata は traq_id のみ。status は forward-compat のため
  z.string()(将来の status で出力検証が落ちないように)。
- 実装: ハンドラで各フィールドを明示的に組み立てる(oRPC のストリップ挙動に依存せず、
  列挙したフィールドしか出さないことを保証)。ID 抽出 idOf / traq_id 抽出 traqIdOf を追加。
- カタログ(Price / Product)は PII を持たないため透過(narrowMetadata)を維持する。
- テスト: fixture に customer_email 等の PII と内部 metadata を入れ、出力に含まれない
  ことを toStrictEqual で厳密に検証。

出力 PII/ネスト metadata の露出面は #15 で挙げていたが、機微リソースについては本 PR で
allowlist により解消した。認可(#15)は引き続き必要。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 全 mutation(prices/products の update、invoices の create)の入力から metadata を
  除去。traq_id はレスポンス専用とし、クライアントによる書き込み(=本人紐付けの偽造)を
  排除する。顧客との紐付けはセッション実装時にサーバ側で行う(#18)。カタログ metadata の
  公開方針は別途検証する。writableMetadata は削除。
- invoices.create: 支払い期限 days_until_due をハードコード(30)から任意入力へ変更
  (期限は運用で決めるため)。リトライ安全のための idempotency_key を任意入力で追加し、
  指定時のみ Stripe のリクエストオプションに渡す。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- checkout.listSessions → checkout.sessions.list(Stripe SDK の
  stripe.checkout.sessions.list の構造に合わせる)。
- 一覧フィルタ・作成入力の param 名を Stripe の bare 名に統一する
  (product_id→product, customer_id→customer, subscription_id→subscription,
  payment_intent_id→payment_intent, price_id→price)。サンプル定義は
  prices が product(bare)・invoices/checkout が customer_id(suffix)で
  不統一だったのを Stripe-exact に揃えた。ハンドラの写像も同名になり単純化。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
traq_id は自前 DB を単一ソースとして持ち、Stripe metadata には持たせない方針に
確定したため、API 出力から traq_id を撤去する。出力で traq_id が必要になれば
customer→DB 逆引きで解決する(#18)。

- カタログ(Price/Product): 透過するが metadata は出力しない(omitMetadata で除く)。
  PriceView/ProductView は Omit<Stripe.X, 'metadata'>。
- 機微(Invoice/CheckoutSession): allowlist から metadata フィールドを外す。
- narrowMetadata / WithTraqId / traqIdOf を撤去(idOf は維持)。
  packages/api-contract/src/views.ts を削除。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
レビュー(6観点)を反映。カタログ(Price/Product)も Invoice/CheckoutSession と同様に
公開フィールドを明示列挙する自作 z.object へ統一し、Stripe SDK の型を公開契約から外す。
これにより (a) 出力の実行時強制(z.custom の素通しを排除)、(b) ネスト metadata の
漏洩ベクタ閉塞、(c) api-contract の stripe 依存の削除、をまとめて達成する。決済プロバイダ
の実装は toXView の背後(薄いアダプタ)に退く。

- Price/Product: 自作 PriceView/ProductView(z.object) + toPriceView/toProductView。
  z.custom / omitMetadata を廃止。api-contract から stripe 依存を削除。
- 一覧レスポンスの {has_more, data} を listEnvelope() に共通化。
- invoices.create の idempotency_key を必須化(多段フロー全体をリトライ安全に)。
- prices.update は active を必須化(単一フィールドなので refine 不要)。
- コメント修正: send_invoice は finalize だけでは顧客にメールを送らない旨に訂正
  (メール送信フローは別 issue)。「写像」等の不自然な表現・冗長を整理。
- stripe util に未設定時の fail-fast を追加。
- テスト刷新: 全 view の allowlist を toStrictEqual で厳密検証、idOf の展開オブジェクト/
  null 分岐、param 分岐(collection_method 等)を追加。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- invoices.create の idempotency_key を max(246) に(実装が末尾に ":finalize" 等の
  suffix を連結して Stripe に渡すため、上限 255 を超えないようにする)。
- Stripe クライアントの apiVersion を SDK が生成された版に明示ピン(省略時にアカウントの
  既定 API バージョンへ従い、SDK の型と実行時レスポンス形状がズレるのを防ぐ)。
- view の doc コメントを正直化: 「決済プロバイダから切り離す」は実態と乖離していたため、
  実際の目的(PII・内部/将来フィールドを漏らさない allowlist と、クライアント向けの安定契約)
  に修正。フィールドの形状・語彙は意図的に Stripe に合わせている旨を明記。
- テスト補強: idOf の展開オブジェクト分岐(prices.product / products.default_price /
  checkout の customer・payment_intent)を各リソースで固定、prices.update の active
  必須の負テストを追加。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
一覧レスポンスを { has_more, data } から { data, next_cursor } に変更する。
カーソル(次回の starting_after に渡す値)の導出はサーバーが担い、続きがあれば
Stripe ソース末尾要素の id、無ければ null を返す。クライアントは next_cursor を
そのまま渡すだけでよく、将来カーソル戦略を変えてもこのレスポンス契約を保てる。

逆方向カーソル ending_before は公開契約から外し、前方ページングのみを提供する。
next_cursor 単体では逆方向の継続位置(先頭要素を ending_before に渡す)を表現できず、
受け付けたまま前方カーソルしか返さないと逆方向ページングが壊れるため。「前に戻る」は
クライアントが starting_after の履歴を保持して実現する。

toListResponse ヘルパで4リソース(prices/products/invoices/checkout)のレスポンス
構築を共通化し、カーソルは View 非依存の Stripe ソース(page.data)の id から導出する。
境界(末尾要素選択・空 data → null・View 非依存のカーソル導出)は views.test.ts で
直接検証する。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
api-contract は Stripe 型を使わない自作 zod ビューに移行済みで、package.json の
依存からは stripe を既に除いていたが、pnpm-lock.yaml が未更新のままだった。lock を
package.json に追従させる。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
移行容易化を主目的にしない旨と、その根拠(入力語彙・カーソル・ハンドラの
書き換えが要る)は PriceView ではなく契約全体に関わる設計判断で、1 つの
View のドキュメントに置くのは非対称でノイズになるため削る。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PriceView コメントから外した設計判断(Stripe 語彙に合わせるが移行容易化は
目的でない旨)を、恒久的な設計判断の置き場である project.md の規約に移す。
allowlist 出力ポリシー自体も従来 View コメントにしか無かったため併せて明文化し、
View 単体でなく契約全体の方針として参照できるようにする。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
「同定データは DB を単一ソースとし Stripe metadata には持たせない」という
API 全体の判断が InvoiceView と CheckoutSessionView のコメントに重複していた。
根拠を project.md の規約に集約し、各 View コメントは「traq_id は出力に含めない」
という View ローカルな事実と参照先だけに絞る。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
collection_method を send_invoice に固定し明示的に確定(finalizeInvoice)する一方で
days_until_due を optional のまま指定時のみ渡していた。Stripe は send_invoice の確定時に
支払い期限を要求するため、未指定の呼び出しは実行時に必ず 400 になり、汎用 500 として
返っていた。契約で必須化し常に渡すことでこの経路を塞ぐ。

テストも更新: 未指定は入力バリデーションで reject、0(即時期限)は 0 のまま透過、
確定 URL が null/undefined の両腕をエラー化することを検証する。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Price.product は Stripe 上で必ず存在する(string | Product | DeletedProduct、削除済みでも
id は残る)が、View が nullable でクライアントに発生し得ない null 分岐を強制していた。
非 null 参照専用の requireIdOf を追加し、priceView.product を z.string() に絞る。

あわせて idOf を null だけでなく undefined も受けるよう広げ、optional な参照
(Product.default_price)呼び出しの `?? null` を不要にして境界を idOf 内に閉じる。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
全プロシージャが無認証の現状で、invoices.create(実際に課金 Invoice を確定し支払い
URL を発行)・prices.update・products.update が同じ無認証ルートに載っており、コード上の
歯止めが無く「main が未到達」だけが安全担保になっていた。

Context に mutationsEnabled を追加し、変更系ハンドラの先頭で assertMutationsEnabled を
呼んで false の環境では FORBIDDEN を返す。Nitro 側は runtimeConfig の enableUnsafeMutations
(既定 false、NUXT_ENABLE_UNSAFE_MUTATIONS=true で有効化)から注入する。認可(#15)導入時に
本来の認可チェックへ置き換える暫定措置。

各変更系に mutationsEnabled:false で拒否されるテストを追加。testContext は既定 true。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
コメント層の why が、契約全体の方針を各 View・各所に重複させたり、書くべきでない対象に
結合させていた。配置を整理する。

- 出力の文字列フィールドを enum で狭めない方針(3 View に重複)と、変更系を既定で無効化し
  #15 で認可に置き換える方針(6 箇所以上に散在)を project.md の規約に集約し、各所は
  固有情報と短い参照だけに絞る。allowlist・移行方針・traq_id と扱いを揃える。
- 契約 invoices.ts の冪等キーコメントが実装の suffix 文字列(":finalize" 等)に結合して
  上限値の根拠を説明していたのを、契約が守る不変条件(suffix 予約分を上限内に収める)の
  記述に変える。#15 の名前空間化注記は実装側の1箇所に寄せる。
- orpc.ts の Context フィールドコメントを参照に短縮し機構説明を assertMutationsEnabled に、
  invoices/prices ハンドラの重複・再説明コメントを削除して4ハンドラの体裁を揃える。
- 日本語の不自然さを修正(明示的に固定する / 対応づけ / falsy / 両ケースを個別に検証)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
repo のどこにも記録が無かった2つの API 規約を正典に残す。

- フィールド名の snake_case 統一(DB 層の camelCase とは別規約で、Stripe 由来フィールドが
  snake_case で不可避なため一貫性を優先する判断)。従来はコード上の慣習でしか伝わらなかった。
- 入力の選択的透過と expand 非受理。出力 allowlist は既に project.md にあるのに入力側の方針が
  params.ts のコメントにしか無く非対称だったため、方針を project.md へ移し params.ts は参照に絞る。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
出力を allowlist にして全フィールドを手で列挙するようになった結果、当初の根拠だった
「透過ゆえに snake_case が避けられない」は成立しなくなった(has_more は next_cursor に
置き換わり出力にも出ない)。現在の実質的な根拠である「入力を Stripe SDK へ 1:1 で渡せ、
出力 View も Stripe と 1:1 で変換層が薄い」に書き換える。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
出力を allowlist(全フィールド手書き)にしたことで、当初 snake_case を選んだ根拠
(Stripe 透過ゆえに不可避)が消えた。消費側(TypeScript/Vue)と DB 層(drizzle)の
慣習に揃え、契約の入出力フィールドを camelCase に統一する。

- 出力 View(unitAmount, amountDue, defaultPrice, paymentIntent 等)と一覧 envelope
  (nextCursor)、create の出力(invoiceId, paymentUrl)を camelCase 化。
- 入力(startingAfter, collectionMethod, daysUntilDue, idempotencyKey, paymentIntent)を
  camelCase で受け、ハンドラで Stripe の snake_case パラメータへ変換して渡す。
- Stripe の enum の値(one_time, send_invoice 等)はデータ値なので変換しない。
- project.md の命名規約を camelCase に更新。消費側 UI は未接続(health.check のみ)のため
  クライアント移行は不要。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
camelCase 移行で追加した入力変換コメントに、既に是正済みの不自然な語「写像」を
再び使っていたため「変換」に統一する。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
規約や契約コメントの理由が「Xにする。Yのため。」の Y が言い換えや浅いジェスチャーに
とどまり、なぜその判断が正しいかを説明できていなかった。各理由を、避けられる具体的な
失敗か退けた代替案とその不利を名指しする形に書き直す。

- project.md: camelCase(退けた snake_case の利点と、allowlist ゆえ自動では得られない点)、
  allowlist 出力(透過が招く PII/内部/将来フィールドの無審査流入)、選択的入力(expand が
  allowlist を迂回する漏洩経路)、enum 非縮小(z.enum 固定が新値で 500 を招く)、変更系ガード
  (「到達不能」だけが歯止めになる危険)を実質的な理由に。
- traq_id: 「metadata に持たせない」と「出力に載せない」を分けて各々正当化(後者は内部同定子の
  露出による名寄せ・相関面の拡大)。
- params.ts: 前方カーソルのみの理由を循環論法から、単一 nextCursor がカーソル導出戦略の差し替え
  自由度を保つこと・ending_before 公開の不利へ。履歴なしクライアントは戻れない制約と双方向への
  拡張パスも明記。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
後で変える時のコストと、今その決定に足る情報があるかの2軸で、暫定にするか今決めるかを
判断する方針を明文化する。暫定は設計で可逆に保ち、見直す条件とともに印を残す。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stripe 系のプロシージャが揃い、health.check の疎通確認としての役目は終えた。契約・ルーター・
ホームページの呼び出し・専用テストを削除し、ホームページは静的にする。

db は現状どのプロシージャも参照しないが、将来のドメイン機能のため Context に残す。Nitro の
遅延ゲッターと README の説明を、health 前提から現状(db 未使用・stripe は全プロシージャで使用)に
合わせて更新する。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
prices は retrieve/list/update を持つのに products は list/update のみで、単一 Product を
ID で取得する手段が契約に無かった。消費側がまだ無く追加コストが小さい今、対称性を揃える
(#25)。allowlist・展開参照の ID 正規化の検証テストも追加。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
このプロシージャは下書きを作るのではなく、価格を項目化し確定(finalize)して支払い可能な
Invoice を発行する金銭的な確定操作。create という名前はその効果を過小に表すため、確定・発行を
表す issue に改める(#26)。消費側がまだ無く改名コストが小さい今に行う。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
reiroop and others added 2 commits August 4, 2026 21:16
シークレット未設定を最初の Stripe 利用時ではなくデプロイ時に検知するため、Nitro の起動
プラグインで存在を検証し、欠落なら起動を失敗させる(#23)。検証は本番のみで、ローカル開発は
鍵なしでも起動できる(Stripe を使うプロシージャは呼ばれた時点で useStripe が明示エラーにする)。

server/plugins は起動時自動登録で静的 import 経路が無く、typescript-eslint の projectService が
所有 tsconfig を見つけられないため、このディレクトリだけ Nitro のサーバー tsconfig を明示する
ESLint 設定を追加する。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
API の消費者は現状 apps/web のクライアントのみで契約と同時デプロイされるため、内部専用・
co-deploy・バージョニングなしを確定する(#35)。あわせて、現状の Stripe 系エンドポイントは
Stripe を薄く包む管理・配管層であって会員向けドメイン API ではないこと、会員向けは別に
ドメイン言語の API を建てて Stripe を裏に隠すこと(#27/#28)を明記する。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@reiroop reiroop added the enhancement New feature or request label Aug 4, 2026
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed #23の要件に対し、起動時検証プラグインを追加し、未設定時の失敗とuseStripeの遅延生成・キャッシュを実装しています。
Out of Scope Changes check ✅ Passed 変更はStripe API公開、起動時設定検証、契約、実装、テスト、設計文書に関係し、リンクされた目的の範囲内です。
Docstring Coverage ✅ Passed Docstring coverage is 94.12% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed タイトルは、Stripeの4リソースをoRPCの契約ファーストAPIとして公開する主要変更を具体的に示しています。
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/prices-orpc-contract

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@reiroop
reiroop merged commit 31a9954 into main Aug 4, 2026
5 of 6 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/nuxt.config.ts`:
- Around line 8-9: Update the runtime configuration flow around stripeSecretKey
so the server-side shared resolver reads the secret at application runtime and
supplies it to runtimeConfig.stripeSecretKey instead of evaluating
process.env['STRIPE_SECRET_KEY'] during nuxt.config.ts build-time loading.
Preserve NUXT_STRIPE_SECRET_KEY support and document a single, consistent
precedence between the two environment variable forms in the README.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6d7a691f-1f24-4651-9e20-51067a7106bf

📥 Commits

Reviewing files that changed from the base of the PR and between a0b78ef and 729b1a3.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (32)
  • README.md
  • apps/web/app/pages/index.vue
  • apps/web/nuxt.config.ts
  • apps/web/package.json
  • apps/web/server/plugins/validate-config.ts
  • apps/web/server/routes/rpc/[...].ts
  • apps/web/server/utils/stripe.ts
  • eslint.config.mjs
  • openspec/project.md
  • packages/api-contract/src/checkout.ts
  • packages/api-contract/src/health.ts
  • packages/api-contract/src/index.ts
  • packages/api-contract/src/invoices.ts
  • packages/api-contract/src/params.ts
  • packages/api-contract/src/prices.ts
  • packages/api-contract/src/products.ts
  • packages/api-contract/src/router.ts
  • packages/api/package.json
  • packages/api/src/checkout.test.ts
  • packages/api/src/checkout.ts
  • packages/api/src/invoices.test.ts
  • packages/api/src/invoices.ts
  • packages/api/src/orpc.ts
  • packages/api/src/prices.test.ts
  • packages/api/src/prices.ts
  • packages/api/src/products.test.ts
  • packages/api/src/products.ts
  • packages/api/src/router.test.ts
  • packages/api/src/router.ts
  • packages/api/src/test-utils.ts
  • packages/api/src/views.test.ts
  • packages/api/src/views.ts
💤 Files with no reviewable changes (3)
  • apps/web/app/pages/index.vue
  • packages/api-contract/src/health.ts
  • packages/api/src/router.test.ts

Comment thread apps/web/nuxt.config.ts
Comment on lines +8 to +9
// 実行時に NUXT_STRIPE_SECRET_KEY で上書き可能。サーバー専用(先頭に public を付けない)。
stripeSecretKey: process.env['STRIPE_SECRET_KEY'] ?? '',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' apps/web/server/plugins/validate-config.ts
sed -n '1,260p' apps/web/server/utils/stripe.ts
rg -n -C 3 'stripeSecretKey|STRIPE_SECRET_KEY|NUXT_STRIPE_SECRET_KEY' apps/web

Repository: traPtitech/Checkin

Length of output: 3331


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

config = Path('apps/web/nuxt.config.ts').read_text()
validate = Path('apps/web/server/plugins/validate-config.ts').read_text()
stripe = Path('apps/web/server/utils/stripe.ts').read_text()

print('nuxt runtimeConfig line 9:')
m = re.search(r"stripeSecretKey:\s*([^,\n]+)", config)
print(m.group(0).strip() if m else '<not found>')

def resolve_runtime_config_key(key):
    # matches common NUXT_ runtime-config override form without public/private suffixes
    return 'NUXT_' + key.upper().replace('-', '_')

print('expected runtime override:', resolve_runtime_config_key('stripeSecretKey'))
print('STRIPE_SECRET_KEY used at runtime?:', bool(re.search(r"process\.env\[['\"]STRIPE_SECRET_KEY['\"]", validate + stripe)))
print('NUXT_STRIPE_SECRET_KEY used in validation error?:', 'NUXT_STRIPE_SECRET_KEY' in validate)
PY

Repository: traPtitech/Checkin

Length of output: 384


STRIPE_SECRET_KEY の設定読み上げを修正してください。

nuxt.config.ts での stripeSecretKey 評価時に process.env['STRIPE_SECRET_KEY'] を参照していますが、ビルド後の実行プロセスへの環境変数を想定するなら、サーバー側の共通 resolver でその値を実行時に読み取って runtimeConfig.stripeSecretKey に渡してください。同じ設定を NUXT_STRIPE_SECRET_KEY で設定する形式も残す場合は、どちらを優先する README を統一してください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/nuxt.config.ts` around lines 8 - 9, Update the runtime configuration
flow around stripeSecretKey so the server-side shared resolver reads the secret
at application runtime and supplies it to runtimeConfig.stripeSecretKey instead
of evaluating process.env['STRIPE_SECRET_KEY'] during nuxt.config.ts build-time
loading. Preserve NUXT_STRIPE_SECRET_KEY support and document a single,
consistent precedence between the two environment variable forms in the README.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment