Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Stripe を包む層の境界を、stripe-customer spec・design.md・openspec/project.md が別々に決めている

Aperta
#95 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
25/100
Tipo di issue
Documentazione
Chiarezza
Da chiarire
Stato di attività
Attiva
Stack tecnologico
typescript

Direzione di ricerca

Start by reading openspec/specs/stripe-customer/spec.md, design.md, openspec/project.md, and the comments in packages/api/src/index.ts and packages/api/src/payments/normalize.ts at the referenced commits. Review related issues #53, #15, and #87 before deciding which boundary and record are authoritative; done means the decision is documented consistently and the affected code comments or specifications no longer contradict it.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

documentation enhancement

症状

Stripe を包む層の境界について、3 つの記録が別々のことを書いている。以下はいずれも 2026-09-20 に c81574a で確かめた。origin/main はその後も進んでいるので、確かめ直すときは c81574a を指定してほしい。

1 つ目は仕様である。 openspec/specs/stripe-customer/spec.md の ### Requirement: Stripe 呼び出しはアダプタ境界に閉じ込める に次がある。

システムは、Stripe SDK の呼び出しをアダプタ層に隔離 SHALL。集金ドメイン(費目・期・価格・認可の判定)は Stripe 型に直接依存しては SHALL NOT。これにより「Stripe オフ→口座振込」や将来の API 移行をアダプタ差し替えで吸収できる。

これは openspec/changes/archive/ ではなく openspec/specs/ にある現行の仕様で、SHALL で書かれている。packages/api/src/stripe/client.ts の先頭コメントは、この要件を名前で引いている((stripe-customer spec: §Stripe 呼び出しはアダプタ境界に閉じ込める))。

2 つ目は design.md である。 「1. 概要と目的」に次がある。

原則: サークルが Stripe に依存せず、いつでも切り替えられること。 Stripe をオフにして口座振込を指示できるよう、ドメインと「人 ↔ Stripe オブジェクト」の対応表は 自前 DB を source of truth とし、Stripe は薄いアダプタ越しに使う。

同じ文書の「3. 全体アーキテクチャ」はより直接に述べている。図は ADP["Stripe アダプタ層"] を 1 つのノードとして置き、Stripe へ向かう辺を ADP --> STRIPE の 1 本だけにしている。図に続く本文は次のとおりである。

ポイントは Stripe アダプタ層 を境界にすること。ドメイン(誰がいくら払う/払い戻す)は Stripe 非依存に保ち、Stripe 呼び出しをアダプタに閉じ込めておけば、「Stripe オフ → 口座振込」への切り替えや、将来の v2 移行がアダプタ差し替えで済む。

同じ文書の「10. 将来: v2 移行の watch ポイント」にも次がある。

  • ドメイン層を Stripe 非依存に保っておけば、移行は アダプタ差し替え で済む。

アダプタ層がどのディレクトリに当たるかは、仕様にも design.md にも書かれていない。 書いているのはアーカイブ済みの提案で、openspec/changes/archive/2026-06-19-add-membership-collection/proposal.md に次がある。

  • Stripe アダプタ層の導入: Stripe SDK 呼び出しを packages/api/src/stripe/ のアダプタに閉じ込め、ドメイン層は Price/金額/期の判定のみを担う(Stripe オフ→口座振込 や将来の v2 移行をアダプタ差し替えで吸収)。

3 つ目は openspec/project.md である。

現状の Stripe 系エンドポイント(prices・products・invoices・checkout)は Stripe を allowlist で薄く包む管理・配管層であって、会員向けのドメイン API ではない。

仕様・design.md・提案の 3 つを合わせると、packages/api/src/stripe/ がアダプタ層であり、Stripe SDK の呼び出しはそこに隔離される。project.md を読むと、その外にある 4 つのルーター自体が「Stripe を薄く包む層」である。 どちらが今の方針かによって、後述の「アダプタの外に出ている」が逸脱なのか意図なのかが変わる。

これは記録の欠落ではなく、記録どうしの食い違いである。 仕様・design.md の 3 箇所・提案・client.ts の仕様参照は、いずれも 71dced4(2026-06-19)で入っている。project.md の上の記述を書いたのは 31a9954(2026-08-04、PR #38)で、4 つのルーターを入れたのと同じコミットである。 つまり 4 つの置き場所は意図して決められ、その意図は project.md に書かれている。新しいほうが project.md で、仕様は SHALL のまま残っている。

「以後変わっていない」は、後続のコミットを挙げて確かめた。 git log --oneline -- design.md が返すのは b33362c(2026-09-19、#77)と 71dced4 の 2 件で、b33362c が design.md で変えたのは mermaid の MAIL[…] の 1 行だけである(git show b33362c -- design.md)。git log --oneline -- openspec/specs/stripe-customer/spec.md が返すのは 71dced4 の 1 件だけである。git log --oneline -- openspec/project.md は 31a9954 のあとに 628a3ce(#40)と 9e8ddb0 の 2 件を返すが、git show 628a3ce -- openspec/project.md と git show 9e8ddb0 -- openspec/project.md のどちらも、上の引用の行に触れていない。git log -S は出現行数を変えない編集を返さないので、後続コミットの有無はこの形で確かめる必要がある。

どの記録で読んでも偽になる記述が 1 つある

packages/api/src/index.ts が ./stripe を再エクスポートする行に、次のコメントが付いている。

Stripe adapter layer (all Stripe SDK usage is isolated here).

SDK のハンドルは Context の stripe.sdk ゲッターから届く。これを呼んでいるファイルは次のコマンドで拾える。

git grep -ln '\.sdk\.' -- 'packages/api/src' ':!*.test.ts'

返るファイルには packages/api/src/stripe/ の外のものが含まれる。checkout.ts・invoices.ts・prices.ts・products.ts が context.stripe.sdk.prices.list(...) のように SDK を直に呼んでいる。

仕様は「Stripe SDK の呼び出しをアダプタ層に隔離 SHALL」、design.md は「Stripe 呼び出しをアダプタに閉じ込めておけば」と、いずれも隔離の対象を呼び出し全体としている。この広さで読むと all Stripe SDK usage is isolated here は成り立たない。 隔離の対象を狭く書いた例もリポジトリにはあり、packages/api/src/stripe/client.ts の先頭コメントは Stripe SDK initialization, isolated to the adapter layer. と初期化に限っている。ただし index.ts の行は all と書いているので、この狭い読みには当たらない。

SDK の呼び出しについては、逸脱しているのはこの 4 つのルーターだけである。 集金ドメインの側は SDK を直に呼んでいない。openspec/specs/membership-billing/spec.md の ### Requirement: 請求書の作成・確定・送付 は「請求書はドメイン非依存のアダプタ越しに作成する。」と課しているが、これを実装している packages/api/src/membership/router.ts の 3 つのプロシージャ(issueInvoice・issueSpecialInvoice・issueSpecialInvoiceByEmail。後ろの 2 つは同じ補助関数を共有している)は、いずれも packages/api/src/stripe/invoices.ts の createDraftInvoice を呼ぶ形になっていて、上のコマンドの返り値に membership/router.ts は含まれない。したがって、SDK の呼び出しを動かす対象は 4 つのルーターに限られる。 同じ要件の 2 文目に反する範囲は別である。 冒頭に引用した段落の 2 文目、集金ドメイン(費目・期・価格・認可の判定)は Stripe 型に直接依存しては SHALL NOT のほうで、次の節で述べるとおり packages/api/src/payments/normalize.ts が Stripe の型をインポートしている。ただし、この文が括弧で挙げている 4 つのどれにも normalize.ts は当たらない。 normalize.ts がしているのは Stripe のオブジェクトから PaymentRow への変換で、費目・期・価格・認可の判定ではない。この括弧が網羅なのか例示なのかを仕様が書いていないので、normalize.ts が「集金ドメイン」に当たるかは決まらない。下の判断に含まれる。

./stripe の外に呼び出しがあるという 事実 は、下の判断を待たずに確かめられる。ただし index.ts に何と書くかは待つ。 この行は仕様・design.md・提案と同じ決定を述べたもので、コメントだけを書き換えると仕様との食い違いが残り、SHALL から外れた状態を正常として記録することになる。

このパターンは client.sdk; s.invoices…、client["sdk"]、const { sdk: api } = client、stripe.sdk?.invoices、.sdk の直後で改行する形のいずれも拾わない。 そこで、大文字小文字を区別しない git grep -inE 'sdk' -- 'packages/api/src' ':!*.test.ts' の返す行を全て読んで裏付けた。.sdk. の形でない一致は、packages/api/src/stripe/client.ts のゲッターの宣言 get sdk(): Stripe { と、packages/api/src/test-utils.ts が Context を組み立てる { sdk: stripe } と、残りはすべて説明のコメントで、呼び出しは 1 つも含まれない。packages/api/src の外は git grep -inE 'sdk' -- . ':!pnpm-lock.yaml' ':!*.test.ts' ':!packages/api/src' で見た。拡張子で絞ると設定ファイルやスクリプトを取りこぼすので、絞らずに走査している(#89)。一致した 11 ファイルのうち .ts は 3 つで、いずれも説明のコメントの中で SDK に言及しているだけである。残る 8 つは .md の文書である。呼び出しは 1 つも無く、git grep -ln '\.sdk\.' -- . ':!pnpm-lock.yaml' ':!packages/api/src' は 0 件、git grep -n 'new Stripe(' -- . ':!pnpm-lock.yaml' が返すのは packages/api/src/stripe/client.ts の 1 行だけである。なお packages/api/src/**/*.ts のようにパスを絞ると packages/api/src の直下が外れ、上の 4 つが消える。

型のインポートは実行時の依存ではない

stripe をインポートしている箇所は次で拾える。

git grep -nE "from 'stripe'" -- '*.ts'

返る行のうち値としてインポートしているのは packages/api/src/stripe/client.ts の import Stripe from 'stripe' だけで、残りはすべて import type Stripe from 'stripe' である。テストを除くと、packages/api/src/stripe/ の外で型をインポートしているのは上の 4 つと packages/api/src/payments/normalize.ts である。

import type は実行時に消えるので、これは実行時の依存ではない。 normalize.ts の先頭のコメントは、これが意図した形だと書いている。

They accept the Stripe object types (so callers stay type-checked) but only read plain fields and OUTPUT plain DTOs — they never call the Stripe SDK. That keeps payments/ importable without pulling in any Stripe runtime, while the adapter (stripe/listing.ts) remains the only place that talks to Stripe.

前半が成り立つのは、packages/api/src/payments/index.ts が ./types・./dashboard・./pagination・./normalize の 4 つだけを再エクスポートし、./router を再エクスポートしていないからである。 payments/ の中で ../stripe をインポートしているのは payments/router.ts(と、それをモックする payments/router.test.ts)だけである(git grep -n '\.\./stripe' -- packages/api/src/payments)。packages/api/src/stripe/index.ts は export * from './client' を含み、client.ts は import Stripe from 'stripe' という値のインポートをするので、payments/router.ts をインポートすれば Stripe のランタイムはモジュールグラフに入る。 前半が言えるのは、payments/ の入口である index.ts がその router.ts を再エクスポートしていないからであって、payments/ の全体が Stripe のランタイムから切れているからではない。

型のインポートが仕様の Stripe 型に直接依存しては SHALL NOT に反するかどうかは、下の判断に含まれる。ただしこの文の後半には、それとは別の問題がある。 the only place that talks to Stripe の主語が payments/ なのかリポジトリ全体なのかで正誤が変わる。前者なら成り立つ。payments/ から Stripe を呼び出す経路は payments/router.ts の import { listCheckoutSessions, listInvoices } from '../stripe' だけで、この 2 つは packages/api/src/stripe/listing.ts にある(git grep -nE 'export (async )?function (listInvoices|listCheckoutSessions)' -- packages/api/src で確かめた)。後者なら成り立たない。stripe/listing.ts のほかにも stripe/connect.ts・stripe/customers.ts・stripe/invoices.ts・stripe/transfers.ts・stripe/webhook.ts が SDK を呼んでいる。範囲を明示して曖昧さを消すことは、下の判断を待たずにできる。

このパターンは、二重引用符の from "stripe"、stripe/... のサブパス、import('stripe')、require('stripe') を拾わない。そこで、この 4 形をすべて含む git grep -nE "(from|import\(|require\() *['\"]stripe" -- '*.ts' でも数えた。-E を落とすとこのパターンは括弧が不均衡だとして失敗する(fatal: command line, ... parentheses not balanced)ので、フラグごとこの形で実行する。広いほうも同じ行数を返すが、同じ行に 2 つの形が同居していれば行数は変わらないので、行数の一致では 0 件を示せない。 返った行を全て読み、いずれも import [type] Stripe from 'stripe' の形だったことで確かめた。

誰が何を誤るか

仕様と design.md だけを読んだ人が、Stripe SDK の呼び出しがアダプタ層に隔離されていると読む。仕様は SHALL なので、読んだ人はそれを満たすべき制約として扱う。 実際には呼び出しが packages/api/src/stripe/ の外にあり、project.md はそれを意図した形として書いている。どの記録を読んだかで、設計の理解と、今の状態が逸脱かどうかの判断が食い違う。

切り替えの費用についても、仕様と design.md だけを読むと過小に見積もる。ただし project.md は、移植性を目的にしていないことを別の箇所で明言している。

フィールドの語彙は Stripe に合わせるが、決済プロバイダの移行容易化は目的にしない(移行時は入力語彙・カーソル・ハンドラの書き換えも要るため、allowlist だけでは移植性は得られない)。

design.md が「いつでも切り替えられること」を原則として掲げることと、この行が「決済プロバイダの移行容易化は目的にしない」と述べることは、同時には成り立たない。 前者を目指す状態、後者を現在の状態として読めば両立するが、どちらの文書もその関係を書いていない。どちらが今の方針かも、下の判断に含まれる。

直さずに残した理由

3 つの記録のどれを正とし、残りをどう直すかの判断が要る。 openspec/project.md は「あらゆる変更は OpenSpec の提案(/opsx:propose)として始まり → 適用(/opsx:apply) → アーカイブ(/opsx:archive) される」と定めているので、仕様を直す場合は design.md と project.md を直すのとは手順も費用も違う。 取りうる手は少なくとも次の 3 つである。

  • 仕様を正とする。 packages/api/src/stripe/ の外にある 4 つのルーターを仕様に合わせて動かすか、この 4 つを仕様の例外として仕様の側に明記する。前者はコードの移動を伴い、後者は project.md の記述をその例外に合わせる必要がある。
  • project.md を正とする。 仕様の SHALL を今の形に合わせて書き換える。この変更は /opsx:propose から始める必要がある。
  • 役割を分けて両立させる。 仕様と design.md を目指す状態、project.md を現在の状態と位置付け、その関係を明記する。仕様の本文は変えずに済むが、SHALL を「まだ満たしていない要件」として扱ってよいかは、OpenSpec の運用の側の判断になる。

加えて、packages/api/src/stripe/ の外にある 4 つのルーターは #53 の対象で、#53 が取りうる手の先頭に挙げているのは「画面から呼ばれていない prices・products・invoices・checkout を契約から外す」である。#53 はその結論が #15 の決める認可の形に依存すると書いている。#53 と #15 が決まる前に 4 つを動かすと、決まった後で作り直しになる。

上に挙げたうち、normalize.ts の一文の範囲を明示することだけが、この判断を待たない。 index.ts の断定は、./stripe の外に呼び出しがあるという事実の確認は待たないが、代わりに何と書くかが記録の側の決定に依存するので待つ。

normalize.ts を直すときは #87 と合わせて行うこと。 #87 は同じファイルの別のコメント(invoiceToRow の中にある Stripe's types declare で始まるもの)について、payments を他の 2 つと分けて書くことを求めている。本 issue が対象にしているのはファイル先頭のコメントで、別の行である。#87 は「そのブランチは normalize.ts を触っておらず、触ると変更の範囲が広がる」として保留しているので、別々に着手すると同じファイルを 2 回に分けて触ることになる。

Lingua principale
TypeScript
Stelle
0
Fork
0
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

  • Include un Dockerfile o un file Docker Compose
  • Nessun modello di pull request
  • Nessuna guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di traPtitech/Checkin

Tutte le issue di traPtitech/Checkin

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.