Appearance
会員管理 テーブル設計
| 項目 | 内容 |
|---|---|
| Issue | #2594 |
| 親 Issue | #2381 有料記事の体制を整える |
| 関連 Issue | #2390 決済・購読・アクセス制御 |
| 最終更新 | 2026-07-04 |
1. 概要
有料記事 Phase 2(自サイト決済・アクセス制御)に向け、会員のプラン加入状態と有料記事の購入記録を管理するカスタムテーブルを定義する。
会員の実体(アカウント情報)は WordPress 標準ユーザー(wp_users / wp_usermeta)を利用し、本設計のカスタムテーブルは「プラン」「加入状態」「購入記録」など WordPress コアが持たないドメインデータのみを保持する。
2. WordPress 標準ユーザーを採用する理由
| 観点 | WordPress 標準ユーザー(採用) | 完全独自会員テーブル(不採用) |
|---|---|---|
| パスワード管理 | core の wp_hash_password / wp_check_password に委譲 | bcrypt/Argon2id 等を自前実装・保守が必要 |
| セッション/Cookie | wp_signon / wp_set_auth_cookie が提供 | セッション管理を自前実装 |
| 既存 WP エコシステム | プラグイン・REST API・Capability との親和性が高い | WP ユーザーと二重管理になり整合性が崩れやすい |
| 実装コスト | 低(認証基盤は core 利用) | 高(セキュリティ監査対象が増える) |
| 決済連携 | WooCommerce 等が WP ユーザー ID を前提とする | 決済プロバイダ連携時にユーザー ID マッピングが必要 |
結論: パスワード暗号化・ログインセッションは WordPress core に委譲し、会員ドメイン固有データのみカスタムテーブルで管理する。
3. ER 図
注: wp_users と paid_article 投稿は WordPress コアテーブル(wp_posts 等)であり、DBML 正本のスコープ外。上図は論理関係の説明用。
DBML 正本: docs/design/テーブル定義/custom-tables.dbml
4. テーブル定義
4.1 db_member_plan_master(会員プラン/商品マスタ)
| カラム | 型 | NULL | デフォルト | 説明 |
|---|---|---|---|---|
id | bigint | NO | AUTO_INCREMENT | 主キー(MySQL では unsigned) |
plan_key | varchar(64) | NO | — | プラン識別子(例: paid_article_single) |
name | varchar(255) | NO | — | 表示名 |
price | int | NO | 0 | 価格(円・税込想定、unsigned) |
billing_cycle | varchar(32) | NO | one_time | 課金サイクル(下表参照) |
is_active | tinyint | NO | 1 | 0: 非公開, 1: 公開 |
sort_order | int | NO | 0 | 表示順 |
created_at | datetime | NO | CURRENT_TIMESTAMP | 作成日時 |
updated_at | datetime | NO | ON UPDATE | 更新日時 |
インデックス: uk_plan_key (plan_key) UNIQUE
billing_cycle の値:
| 値 | 用途 |
|---|---|
one_time | 単号購入(有料記事 1 号) |
monthly | 月額サブスク |
yearly | 年額サブスク |
定数(実装時): DatabaseTableConstants::MEMBER_PLAN_MASTER = 'db_member_plan_master'
4.2 db_member_subscription(会員プラン加入状態)
| カラム | 型 | NULL | デフォルト | 説明 |
|---|---|---|---|---|
id | bigint | NO | AUTO_INCREMENT | 主キー |
user_id | bigint | NO | — | WordPress wp_users.ID(論理 Ref、DB FK なし) |
plan_id | bigint | NO | — | db_member_plan_master.id |
status | varchar(32) | NO | active | 加入状態(下表参照) |
started_at | datetime | NO | — | 加入開始日時 |
expires_at | datetime | YES | NULL | 失効日時(単号購入は NULL = 永久) |
external_provider | varchar(32) | NO | none | 決済プロバイダ(none / stripe / woocommerce) |
external_subscription_id | varchar(255) | YES | NULL | プロバイダ側サブスク ID(Webhook 連携用) |
created_at | datetime | NO | CURRENT_TIMESTAMP | 作成日時 |
updated_at | datetime | NO | ON UPDATE | 更新日時 |
インデックス:
idx_user_id (user_id)idx_status (status)idx_user_plan_status (user_id, plan_id, status)
status の値:
| 値 | 説明 |
|---|---|
active | 有効(閲覧権あり) |
canceled | 解約済み(expires_at まで有効な場合あり) |
expired | 失効 |
suspended | 管理者による一時停止 |
定数(実装時): DatabaseTableConstants::MEMBER_SUBSCRIPTION = 'db_member_subscription'
4.3 db_paid_article_purchase(有料記事単号購入記録)
| カラム | 型 | NULL | デフォルト | 説明 |
|---|---|---|---|---|
id | bigint | NO | AUTO_INCREMENT | 主キー |
user_id | bigint | NO | — | WordPress wp_users.ID |
post_id | bigint | NO | — | paid_article CPT の投稿 ID |
price | int | NO | 0 | 購入時の価格(円) |
external_provider | varchar(32) | NO | none | 決済プロバイダ |
external_transaction_id | varchar(255) | YES | NULL | プロバイダ側取引 ID(Webhook 冪等キー) |
purchased_at | datetime | NO | — | 購入完了日時 |
created_at | datetime | NO | CURRENT_TIMESTAMP | 作成日時 |
updated_at | datetime | NO | ON UPDATE | 更新日時 |
インデックス:
uk_user_post (user_id, post_id)UNIQUE — 同一ユーザー・同一号の二重購入防止idx_user_id (user_id)idx_post_id (post_id)uk_external_transaction_id (external_transaction_id)UNIQUE — Webhook 冪等キー(非 NULL 値の重複防止。MySQL では NULL は UNIQUE 比較対象外のため複数 NULL 可)
定数(実装時): DatabaseTableConstants::PAID_ARTICLE_PURCHASE = 'db_paid_article_purchase'
5. WordPress コアテーブルとの連携
5.1 論理参照の原則
本プロジェクトの DBML 方針に従い、DB レベルの外部キー制約は設けない。Repository 層で以下を担保する。
| 参照元カラム | 参照先 | 整合性チェック(実装時) |
|---|---|---|
user_id | wp_users.ID | ユーザー存在確認(get_userdata() 等) |
post_id | wp_posts.ID | post_type = 'paid_article' かつ公開状態の確認 |
plan_id | db_member_plan_master.id | マスタ存在確認 + is_active = 1 |
5.2 wp_usermeta で補完するデータ(設計上の候補)
カスタムテーブルに載せず、ユーザーメタで管理する軽量データの例:
| meta_key | 用途 |
|---|---|
member_email_verified | メール確認済みフラグ(0 / 1) |
member_email_verify_token | メール確認用ワンタイムトークンの HMAC-SHA256(平文はメール/URL のみ。鍵は wp_salt( 'auth' )) |
member_terms_accepted_version | 同意済み利用規約バージョン(DB 現行 terms.version と一致で同意済み。空時フォールバックは MemberTermsConsentConstants::CURRENT_VERSION) |
member_terms_accepted_at | 利用規約同意日時(UNIX タイムスタンプ文字列) |
member_registered_at | 会員登録日時(wp_users.user_registered でも可) |
6. 購入記録とサブスクの使い分け
| ユースケース | 使用テーブル | 備考 |
|---|---|---|
| 有料記事 1 号の購入 | db_paid_article_purchase | 永久アクセス(#2384 方針) |
| 月額プレミアム会員 | db_member_subscription | billing_cycle = monthly、expires_at で失効 |
| 全号アクセス権 | db_member_subscription | 特定プラン加入で全 paid_article を閲覧可 |
単号購入は db_paid_article_purchase で管理し、プラン加入型の権利は db_member_subscription で管理する。
7. 拡張ポイント
| 拡張 | 対応方針 |
|---|---|
| 月額サブスク | db_member_plan_master.billing_cycle = monthly + db_member_subscription |
| 返金 | db_paid_article_purchase に refunded_at 列追加、または status 列追加 |
| ゲスト購入 | 非推奨(#2384: WP ログイン必須推奨)。必要なら別テーブルで email 紐付け |
| ログイン試行監査 | ログイン試行監査テーブル-検討.md 参照 |
| WooCommerce 連携 | external_provider = woocommerce、注文 ID を external_transaction_id に保存 |
| Stripe Webhook 冪等 | uk_external_transaction_id UNIQUE 制約で二重付与防止(非 NULL 時のみ。external_provider = none 等で NULL の行は複数可) |
8. 実装参照
| レイヤー | ファイル例 |
|---|---|
| 定数 | core_src/Constants/DatabaseTableConstants.php |
| Installer | core_src/Infrastructure/Database/MemberPlanMasterInstaller.php 等 |
| Entity | core_src/Model/Entity/member_plan_master_entity/ 等 |
| Repository | core_src/Model/Repository/member_plan_master_repository/ 等 |
| Interface | core_src/Interface/repository/MemberPlanMasterRepositoryInterface.php 等 |
| SQL | core_src/Model/Sql/member_plan_master/ 等 |
| DI | core_src/config/di/repositories.php |