決済プロバイダーは、遅れて請求を確認したり、同じイベントを複数回送信したり、処理途中の取引を残したりすることがあります。そのため、「支払済み」と「アクセス可能」は同義ではありません。アカウントの権限が決済APIから最後に受信したレスポンスに直接依存している場合、一時的な障害により、実際には支払った顧客をブロックしたり、請求が最終的に失敗した別の顧客を有効化したりする可能性があります。
PHP SaaSのサブスクリプション状態では、目標はテーブルに単一のラベルを保存することではありません。復旧可能なプロセスを構築することです。すべての判断には、証拠、担当者、有効な遷移、新しいデータが到着した際に照合する手段が必要です。
技術モデルより先にプロダクトルールを定義する

データモデルは商業上の曖昧さを解決しません。エンティティやwebhookを設計する前に、プロダクト、財務、オペレーションは、関連する各状況で何が起こるかについて合意する必要があります。
- 開始:初回の支払いが確認される前、オーソリゼーション後、または決済完了後にのみアクセスを付与するのか。
- 更新:猶予期間はいつ開始し、その間にどの機能を維持するのか。
- 未払い:自動再試行、通知、部分的な制限、または完全停止はあるのか。
- 解約:アクセスは即時に終了するのか、それともすでに契約済みの期間の終了時か。
- 返金または異議申立て:即時ブロック、手動レビュー、または結果の確定時の取り消しが必要か。
- 再有効化:以前のプランを正確に復元するのか、新たな商業サイクルを作成するのか、それとも運用上の検証を必要とするのか。
顧客が要求した解約と、実際に有効となった解約も区別することが重要です。前者は意図を表し、後者は将来のアクセス権を変更します。これらを混同すると、分かりにくいインターフェースと修正が難しい自動化を招きます。
契約、請求、実効アクセスを分離する
保守可能なアーキテクチャでは、少なくとも4つの概念を表現します。アカウントは契約主体とそのメンバーを識別します。商業契約はプラン、合意価格、更新日、解約の決定を記述します。請求サイクルは、ある期間に対する具体的な債務、その金額と結果を表します。最後に、有効化された機能は、アカウントがプロダクト内で何を実行できるかを具体化します。
この分離により、決済プロバイダーをSaaS全体の唯一の信頼できる情報源にしてしまうことを防げます。契約が猶予によって有効なままであれば、サイクルは保留中でもかまいません。同時に、アカウントは閲覧アクセスを維持しつつ、新しいリソースを作成できない場合があります。機能によって、アクティブか非アクティブかという誤った二択を強いることなく、この判断を表現できます。
PHPでは、アプリケーションは、たとえばcanCreateProjectやcanExportDataなど、機能のローカルプロジェクションを参照する認可サービスを公開できます。このプロジェクションは商業上または請求上の事実が変わったときに更新され、リクエストごとにプロバイダーを呼び出す必要はありません。これにより、レイテンシ、外部依存、コントローラー、キュー、スケジュールされたタスク全体に散在する条件分岐を減らせます。
遷移、担当者、証拠をモデル化する
インシデントが発生するたびに値を追加する、単一のstatusフィールドは避けてください。集約ごとに状態と許可された遷移を宣言するほうが望ましいです。たとえば、請求サイクルはopenからpayment_pending、paid、failed、refunded、disputedへ遷移できます。すべての遷移が可逆であるわけではなく、どのアクターでも実行できるわけではありません。
各変更には、日付、発生元、存在する場合は外部識別子、証拠を保存する必要があります。発生元は、内部注文、検証済みwebhook、照会クエリ、または認可された手動操作です。サポートによる修正は履歴を黙って上書きしてはなりません。理由と担当オペレーターを伴う別の判断として記録する必要があります。
矛盾する情報に対する優先順位
どの証拠を優先するか定義してください。支払い後のリダイレクト画面でサイクルを確定してはなりません。これはユーザーへの通知には役立ちますが、最終的な証拠ではありません。署名済みかつ検証済みのwebhookは通常、より良いシグナルを提供しますが、遅れて到着する場合があります。照合中にプロバイダーへ行う認証済みの照会は、欠落したイベントを明らかにできます。2つの情報源が一致しない場合、システムはレビューまたは定義済みの保留状態へ移行すべきであり、最も新しいデータを恣意的に選択してはなりません。
遅延、重複、不完全なイベントを処理する
イベントの受信は冪等でなければなりません。外部イベントの安定した識別子と、関連するペイロードのハッシュまたは参照を保存してください。再受信した場合は、ビジネス上の効果を繰り返さずに応答します。これは、決済イベントが文書の発行、期間の延長、通知をトリガーする場合に特に重要です。
処理では受信と適用を分離する必要があります。まず署名、スキーマ、送信元を検証し、次に受信イベントを永続的に保存し、最後に遷移の適用を試みるタスクを処理します。イベントを永続化した後にプロセスが停止しても、キューまたは復旧プロセスで再開できます。永続化前に失敗した場合、照合は内部サイクルと外部ソースを比較して差異を発見する必要があります。
イベント受信 → 検証 → 永続的な記録 → 冪等な適用
↓
再試行または照合配信順序を前提にしてはなりません。元の支払いの遅延確認より前に返金が届くことがあります。ルールは現在の状態、取引参照、既知の順序を評価し、不可能または曖昧なケースはレビューキューに残す必要があります。「最後に受信したイベント」を盲目的に適用することは、誤った権限の一般的な原因です。
照合と権限を制御されたプロジェクションとして扱う
定期的な照合はパッチではなく、設計の一部です。長時間オープンのままのサイクル、システム外で確認された支払い、記録済みで未処理のイベント、重複した外部参照、現行契約に対応しない機能を特定する必要があります。差異を検出した場合は、フィールドを直接更新するのではなく、検出結果を記録し、追跡可能な遷移を適用してください。
機能のプロジェクションには明示的なルールが必要です。たとえば、有効な契約でサイクルは期限切れでも猶予期間内であれば、重要な機能を維持できます。猶予終了時には、書き込み操作を取り消すことができます。遅延支払いが確認された場合、システムはプランに予定されている機能を再有効化し、それ以前の制限の履歴を保持します。
権限キャッシュは有用な場合がありますが、プロジェクションの変更時の無効化と有効期限の上限が必要です。重要な認可を、ブラウザーに保存されたデータだけに基づかせてはなりません。サーバーは、現在有効な機能と、アカウント、ユーザー、リソースの正しいスコープに基づいて判断する必要があります。
バックオフィス、監査、復旧テスト
サポートチームは、データベースレコードを編集せずに、契約、サイクル、外部イベント、適用済み遷移、現在の機能、手動操作を確認できる必要があります。照合の実行、安全なイベントの再試行、レビューの開始を要求できなければなりません。アクセスまたは残高を変更する修正には、権限の分離、理由の必須化、監査記録が必要です。
フローは正常な支払いだけでなく、障害の連鎖としてテストしてください。確認済みの更新、不確実な支払い、重複、順不同のイベント、返金、期間終了時の解約、再有効化を含めます。最終結果だけでなく、いかなる再試行でも2つの期間、2つの文書、または権限の二重延長が作成されないことを検証してください。
仮想的なケースとして、あるサイクルが期限を迎え、請求は保留となり、アカウントは制限された機能で猶予期間に入ります。一時的な中断により確認webhookは処理されませんが、イベントは記録されたままです。冪等な再試行によりサイクルを確認し、契約を延長して機能を再構成します。イベントが届かなかったとしても、照合が確認済みの外部取引を見つけ、独自の証拠を伴って同じ遷移を生成します。
警告シグナルとチェックリスト

契約と機能に不整合があるアカウント、判断のない期限切れサイクル、未処理イベント、再試行の枯渇、照合で検出された差異、手動変更の頻度を測定してください。手動修正の増加は、単なる運用上の問題ではなく、ルール不足を示すことが多いです。
- 契約、請求サイクル、機能は別個のエンティティか。
- 各遷移にアクター、証拠、日付、理由があるか。
- 外部イベントは冪等で、適用前に保存されるか。
- 不完全な取引を復旧できる照合は存在するか。
- 権限はリアルタイムの決済レスポンスではなく、ローカルプロジェクションから算出されるか。
- サポートは本番環境への直接変更なしに、監査付きで調査・修正できるか。
- テストは遅延、重複、順不同、矛盾をカバーしているか。
復旧可能なモデルは外部障害をなくすものではありません。請求インシデントをプロダクトへのアクセス制御の喪失に変えることなく、障害を検出可能、限定可能、修正可能にします。



