Skip to content

fix(order): 人間のモバイル注文 checkout の支払いを注文に束縛し公開 notify の横取りを塞ぐ (A2c・掟 15) - #607

Merged
cipherwebllc merged 1 commit into
mainfrom
fix/r6-a2c-checkout-binding
Sep 24, 2026
Merged

cipherwebllc merged 1 commit into
mainfrom
fix/r6-a2c-checkout-binding

Conversation

@cipherwebllc

Copy link
Copy Markdown
Owner

概要

第 6 回レビュー A2 のうち、人間のモバイル注文 checkout 側の残り (P1) です。これまでは、公開の order/notify に他人の支払い tx を添えると、その支払いを自分の注文として登録できました。
user 承認済みの方針 (relay/recover は intentSalt で束縛・標準 ERC-20 は残余として受容・設計 v0.2) で塞ぎます。

⚠️ 掟 15: money-path (注文登録・notify の検証) の変更です。merge 前に user の明示レビューをお願いします。

仕組み

  • relay/recover の支払いは、署名の nonce (intentSalt) を「注文 digest + ブラウザだけが持つ秘密」の hash にします。注文 ID・メモ・状態 token は署名前に固定します。
  • notify はその秘密を添えて送ります。server は on-chain の Settled/AuthorizationUsed の nonce と照合してから、注文・手数料・push の処理に進みます。
  • 未送信の注文記録は、署名後で broadcast 前に保存します。保存できなければ broadcast しません。notify が受理されたら記録を消します。
  • flag ENABLE_ORDER_BIND_ENFORCE (server 専用・既定 OFF)
    • OFF: 添えられた束縛は検証し、壊れた束縛は 422 で拒否します。束縛のない relay 注文は、店舗画面に「結びつき未確認」の注意を出して受け付けます。
    • ON: relay/recover の束縛を必須にします。点灯は user の判断です。

公開文言 (承認をお願いします)

key 日本語 English
CheckoutForm.previousPaymentConfirming 前回の支払いを確認中です。まだ支払い直さないでください。 Your previous payment is still being confirmed. Please do not pay again yet.
CheckoutForm.previousPaymentUnconfirmed 前回の支払いの状態を確認できませんでした。支払い直す前にお店のスタッフに確認してください。 We could not confirm your previous payment. Check with store staff before paying again.
CheckoutForm.previousPaymentConfirmingOther 以前のお支払いを確認中です。このお店へのお支払いは続けられます。 A previous payment is still being confirmed. You can still pay this store.
CheckoutForm.orderBindingRestoreUnavailable 以前の注文の復元情報を読み込めませんでした。支払い済みの場合は、支払い直さずお店のスタッフに確認してください。 Previous order recovery information could not be read. If you already paid, check with store staff before paying again.
CheckoutForm.orderBindingStorageUnavailable 注文の復元情報をこのブラウザに保存できないため、支払いを開始できません。ブラウザの保存設定を確認してください。 Payment cannot start because this browser could not save the order recovery information. Check your browser storage settings.
CheckoutForm.retryOrderNotify 注文登録を再試行 Retry order registration
CheckoutForm.previousOrderNotifyPending 以前の注文の登録が保留中です。このお支払いは続けられます。 A previous order is awaiting registration. You can still make this payment.
CheckoutForm.previousOrderNotifyStaff 以前の注文を登録できませんでした。そのお店のスタッフに支払い履歴またはトランザクションを提示してください。 A previous order could not be registered. Show its payment history or transaction to that store's staff.
OrderBinding.unverified (店舗画面) 支払いと注文の結びつき未確認 — 受け渡し前に手動確認が必要 Payment/order association unverified — manual verification required before handover
OrderBinding.scope (店舗画面) 標準 ERC-20 送金と relay を使わない ERC-4337/7702 ガスレス決済では、支払いと注文の結びつきは暗号学的に検証されません。 Standard ERC-20 transfers and non-relay ERC-4337/7702 gasless payments do not cryptographically verify the association between payment and order.

既存の文言を新しい場面でも使います (文言は不変): CheckoutForm.orderNotifyTooOld・CheckoutForm.urlInvalidTitle。

README の env 表と .env.local.example に ENABLE_ORDER_BIND_ENFORCE を追加しました (掟 9)。

掟 12 の例外 (既存の挙動が変わる点)

  • 束縛つきの relay/recover 認可は、nonce で正規化した注文に結びつきます。受け取り通知の flag が OFF のときは、状態 token を空にして notify から省きます。
  • 束縛つきの注文は、支払いごとの記録と checkout 文脈の照合で復元します。通常の支払いの復元には乗りません。
  • 別の checkout の記録は読み取り専用で解決し、背景で自サイトの notify を送ります。現在の checkout の完了にはしません。このため、第三者の店の checkout に前の注文の案内が出ることがあります。
  • 同じ店で未解決の記録があるあいだは、その店への新しい支払い (標準・USDC を含む) を止めます。期限が来たら状態を 1 回読み、それでも未解決ならスタッフへの確認を案内して解除します。他の店には支払えます。
  • 標準決済も含めて、notify の自動再試行は 409 processing のときだけにしました。 通信エラー・5xx・429・422 tx_not_found は再試行できます。それ以外は終端で、束縛つきの注文ではスタッフへの案内を出します (JSON でない 4xx と bindingConflict を含む)。
  • 終端の記録は復元対象から外し、新しい注文を止めません。次の読み込みで記録を消します。
  • 束縛つきの successUrl への遷移は、notify の受理を待ってから行います。checkout.success の計測は従来どおり送ります。
  • 束縛つきの request は、重複や処理中の判定よりも前に束縛を検証します。束縛のない後続は、従来どおり重複・409 を即時に返します。
  • 壊れた束縛や一致しない束縛は、422 order_binding_mismatch を返します。
  • 検証済みの notify が、保存済みの束縛なしの注文と衝突したら、応答に bindingConflict: true を追加します。この照合は上限つきの注文一覧に限り、照合の読み取りに失敗したら 503 kv_error を返します。

⚠️ 点灯前の注意 (N8)

ENABLE_ORDER_BIND_ENFORCE を ON にすると、旧来の agent-order/pay の allowLegacy 経路は束縛なしの notify を送るため 422 になります。支払い自体は済んでいても、外側の応答が orderRegistered:false になり得ます。点灯前に運用の確認が必要です (本 PR は既定 OFF)。

受容した残余

  • 標準 ERC-20 と、relay を使わない ERC-4337/7702 の支払いは束縛されません (user 承認済み)。flag OFF では観測のみで、横取りを完全には防げません。
  • 衝突の検出は上限つきの注文一覧の範囲だけです。一覧から落ちた束縛なしの注文とは照合できません。
  • 同じタブでモバイル注文から /pay・/tip に移ると、その注文の記録は復元されず、もう一度支払えてしまいます。
  • CheckoutForm の instance を client の遷移で使い回すと、前の送信中の payload が残ります (既存の挙動)。
  • ブラウザの記録を失うと、スタッフの確認が必要になることがあります。

検証 (base = 3b20582)

  • typecheck 0 / full vitest 14,292 pass・0 fail / 実 Lua 627 pass / build OK / check-lua-bundle OK
  • bundle (First Load・main 比): /pay 412 (+1)・/tip 411 (+2)・/checkout 417 (+3)・/[handle] 449 (+1)・/ 264 (+1) kB。全ルート予算内
  • レビュー: Codex 実装 → 別セッション Opus 5.5 のコードレビュー 4 回 (1〜3 回目 request-changes: 未送信注文の他ページでの復元・別の店での誤表示・同じ店での二重払い → 4 回目 approve-with-fixes) → Codex 修正 5 回 → Opus 5.5 の最終監査

🤖 Generated with Claude Code

https://claude.ai/code/session_01HeizmagJBgL5peL5mQpxkc

@vercel

vercel Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
openpay Ignored Ignored Preview Sep 24, 2026 2:40pm UTC

Request Review

第 6 回レビュー A2 の人間の checkout 側 (P1)。user 承認済みの方針 (relay/recover は intentSalt で束縛・
標準 ERC-20 は残余として受容) と設計 v0.2。intentSalt を注文 digest とブラウザだけが持つ秘密の hash にし、
notify で秘密を添えて on-chain の Settled/AuthorizationUsed の nonce と照合する。
未送信の注文記録は署名後・broadcast 前に保存し、通知の受理で消す。
flag ENABLE_ORDER_BIND_ENFORCE (server・既定 OFF) は束縛の欠落を注意表示で受け、ON で拒否。

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeizmagJBgL5peL5mQpxkc
@cipherwebllc
cipherwebllc force-pushed the fix/r6-a2c-checkout-binding branch from 776e6c8 to 2c45570 Compare September 24, 2026 14:40
@cipherwebllc
cipherwebllc merged commit 4002abd into main Sep 24, 2026
8 checks passed
@cipherwebllc
cipherwebllc deleted the fix/r6-a2c-checkout-binding branch September 24, 2026 15:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant