You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概要
melon-dbの返金処理restore()(refund/void共通実装)に、独立した金銭正当性バグを 2 件見つけたので修正しました。restore()の隣接コードを触るため 1 本の PR にまとめています。バグ ① 並行返金による過剰返金
決済行をロックせず「返金済み額」を集計していたため、同一決済への並行返金が上限チェックの競合状態を通過し、過剰返金が成立していました。バケット共有時(例: ¥1000 バケットから ¥500 決済 2 件)に発生し、バケット側の
CHECK (remaining_amount <= original_amount)(migrations/0001_init.sql:59)は合計がバケット原資を超えない限り発火しないため捕まりません。pay()が候補バケットに既に行っている防御(ops.rs:1258のFOR UPDATE)を、restore()の決済行にも適用しました。再現テスト
concurrent_refunds_never_over_refundは修正前 10/10 回失敗(典型例: 5 件中 4 件が成功してしまう)。バグ ② 失効済みバケットへの返金
失効・スイープ済みバケットへ返金すると
statusが'active'に戻る一方expires_atは過去のまま残ります。balance()はexpires_at > nowでフィルタする(ops.rs:343)ため、利用者からは残高が消えたまま見えず、次回スイープで同じ金額が再度失効益に計上されます(二重計上)。返金計画の段階で対象バケットの
expires_atを確認し、過去なら新エラーDbError::RefundIntoExpiredBucket(422REFUND_INTO_EXPIRED_BUCKET)で返金全体を拒否するようにしました。部分成立は許しません。再現テスト
refund_does_not_resurrect_an_expired_bucketで、修正前は返金が成功し直後の再スイープで同額(¥400)が再計上されることを確認しています。変更
fix refund race condition— バグ①reorder refund restoration steps— 挙動不変のリファクタ(バグ②の下ごしらえ、返金計画を書き込み前に確定)reject refund into an expired bucket— バグ②、およびmelon-server/docs/api.md/端末 UI への反映各コミットは単体で clippy・全テストが通ります(
git rebase --execで確認)。検証
cargo fmt --all --check/cargo clippy --workspace --all-targets --locked -- -D warnings/cargo test --workspace --locked全通過既知の制約
フォークからの PR には
FELICA_RS_TOKENリポジトリシークレットが渡らないため、この PR の CI (.github/workflows/ci.yml) は失敗します(ワークフロー冒頭のコメントに明記されている既知の制約)。ローカルでは上記の通り全チェック通過済みです。