Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

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

Đang mở
#95 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức phù hợp với người mới
25/100
Loại issue
Tài liệu
Độ rõ ràng
Cần làm rõ
Mức độ hoạt động
Sôi nổi
Công nghệ
typescript

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

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 回に分けて触ることになる。

Ngôn ngữ chính
TypeScript
Star
0
Fork
0
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Chuẩn bị môi trường

  • Có Dockerfile hoặc tệp Docker Compose
  • Không có mẫu pull request
  • Không có hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của traPtitech/Checkin

Tất cả issue của traPtitech/Checkin

Issue tương tự

Thêm issue về TypeScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.