注文内容の検証と承認
承認済みという一つのフラグだけで済ませず、承認対象と確定した記録を対応付けます。
説明
購入処理では、数量、単価、承認対象、確定状態をサーバー側で対応付けます。ログイン済みであることだけでは、内容を変えた注文の確定まで許したことにはなりません。
単価100円の商品を使い、クライアントの申告価格、数量の境界、承認後の変更を順に試します。承認した主体・商品・数量・単価・版を保存し、確定時に同じ内容か照合します。
承認済みという一つのフラグだけで済ませず、承認対象と確定した記録を対応付けます。
1サーバーが持つ商品価格と注文を読む
注文1はaliceの商品7、数量2です。単価はcatalogから取得します。
from sd12 import *
lab = Orders()
print(lab.view()注文とサーバー単価を結合して読む)順に注文ID、主体、商品、数量、単価、合計、版、状態です。合計は2×100=200、状態はdraftです。
2申告価格を変えてもサーバーで金額を決める
数量を3、申告価格を1円として渡します。
print(lab.change(3,client_price=1低い申告価格を渡す))
print(lab.view()保存された数量と計算結果を読む)合計は3×100=300です。client_priceは計算に使いません。表示用の価格を送らせる場合も、確定時の正本をサーバー側で決めます。
3数量の値と型を検査する
負数、上限超過、真偽値を試します。
for qty in (-1,6,True)業務上限と型の境界を試す:
print(repr(qty),lab.change(qty))
print(lab.view())すべてinvalid_quantityで数量3、版2のままです。負数による金額反転や、真偽値の整数扱いを入口で避けます。
4承認前の確定と他者の承認を拒否する
注文を作っただけでは確定できません。
print(lab.confirm("alice")承認を飛ばした確定を試す)
print(lab.approve("bob")別主体からの承認を試す)
print(lab.db.execute("SELECT COUNT(*) FROM paid").fetchone())approval_mismatchとsubject_rejectedで、支払記録は0件です。主体と工程の両方を確認します。
5現在の内容を承認して照合値を保存する
aliceの承認を、対象内容のハッシュへ結び付けます。
print(lab.approve("alice")現在の対象内容を承認として保存する)
print(lab.db.execute("SELECT length(approved),approved=?現在の内容との一致を照合する FROM orders",(lab.fingerprint(),)).fetchone())64桁の照合値が現在内容と一致します。このハッシュはサーバーの信頼するDBに保存する比較値で、ハッシュ単体が承認者の署名になるわけではありません。
6承認後の変更には再承認を求める
数量を4へ変更します。変更処理は旧承認を消し、版を進めます。
print(lab.change(4)承認した内容を変更する)
print(lab.confirm("alice")旧承認での確定を試す)
print(lab.view())
print(lab.db.execute("SELECT COUNT(*) FROM paid").fetchone())合計400、版3となり、確定はapproval_mismatchです。300円の注文への承認を400円の確定に流用していません。
7再承認した内容を一度だけ確定する
変更後を承認し、同じトランザクション内で照合と確定を行います。
print(lab.approve("alice"),lab.confirm("alice")変更後の内容を承認して確定する)
print(lab.confirm("alice"),lab.change(2))
print(lab.db.execute("SELECT * FROM paid確定額と件数を確認する").fetchall())400円が1件だけ保存され、再確定と確定後変更はstate_rejectedです。失敗後も確定済みの記録を保っています。
まとめ
価格はサーバーの正本から計算します。
承認を主体・対象・数量・金額・版へ結び付けます。
内容変更後は再承認し、確定を一度に制限します。
操作履歴と解説を振り返れます。
Python 3.15 / 固定の公開試料
from sd12 import *
lab = Orders()
print(lab.view())(1, 'alice', 7, 2, 100, 200, 1, 'draft')
print(lab.change(3,client_price=1))
print(lab.view())changed (1, 'alice', 7, 3, 100, 300, 2, 'draft')
for qty in (-1,6,True):
print(repr(qty),lab.change(qty))
print(lab.view())-1 invalid_quantity 6 invalid_quantity True invalid_quantity (1, 'alice', 7, 3, 100, 300, 2, 'draft')
print(lab.confirm("alice"))
print(lab.approve("bob"))
print(lab.db.execute("SELECT COUNT(*) FROM paid").fetchone())approval_mismatch subject_rejected (0,)
print(lab.approve("alice"))
print(lab.db.execute("SELECT length(approved),approved=? FROM orders",(lab.fingerprint(),)).fetchone())approved (64, 1)
print(lab.change(4))
print(lab.confirm("alice"))
print(lab.view())
print(lab.db.execute("SELECT COUNT(*) FROM paid").fetchone())changed approval_mismatch (1, 'alice', 7, 4, 100, 400, 3, 'draft') (0,)
print(lab.approve("alice"),lab.confirm("alice"))
print(lab.confirm("alice"),lab.change(2))
print(lab.db.execute("SELECT * FROM paid").fetchall())approved paid state_rejected state_rejected [(1, 400)]
演習完了