冪等性と順序
冪等とは、同じ操作を繰り返しても結果が 1 回分と同じになることです(用語集)。
再送でもイベントのボディと id は変わりません。署名検証済みボディの id に UNIQUE 制約を設けて重複排除します。 Stbl-Pay-Event-Id ヘッダは署名対象外なので、冪等キーに使いません。
テスト通知は先に分ける
署名の検証が済んだら、inbox に保存する前に、生の本文を JSON.parse した値で test === true かを判定します。テスト通知は注文を変えず 2xx だけを返します(テスト通知)。
受信と処理を分ける
受信ハンドラは検証したイベントを inbox に永続化し、その後で 2xx を返します。保存したことと業務処理が完了したことを、別の状態として記録してください。
CREATE TABLE stbl_pay_inbox (
event_id TEXT PRIMARY KEY,
payload JSONB NOT NULL,
received_at TIMESTAMPTZ NOT NULL DEFAULT now(),
processed_at TIMESTAMPTZ
);
再送が届いたときに既存の未処理イベントがあっても、新たな行は作りません。ワーカーが未処理行を再試行できるようにします。未知の種別は注文を変更せず無視します。
業務処理のトランザクション
同じ DB トランザクションで次を行います。
- イベントと対応する注文をロックし、処理済みか確認する。
- 決済 ID・加盟店・金額と注文の対応を確認する。不一致は調査用に保留する。
payment_intent_idに対応する注文が無い通知(店頭の QR コード決済など)は、保留せず、注文を変えずに処理済みにする。 - 注文を条件付きで更新し、商品提供などの outbox を一意な注文キーで記録する。
- イベントに処理済み時刻を記録する。
外部処理はコミット後にワーカーから呼び、外部サービスにも冪等キーを渡します。状態照会や手動処理も同じ注文更新経路を通し、イベント ID が違っても二重出荷しないようにします。
順序
再送を含めて到着順序は保証されません。created_at の大小だけで注文を上書きせず、決済の終端状態と注文の対応を確認します。同じ決済に異なる終端結果が届くなどの不整合は、自動処理せず状態照会と調査へ回してください。