Skip to content

test(checkout): 受注通知の 4 経路の応答の表と、自社 URL の判定の表を固定する (PR-12) - #1095

Open
cipherwebllc wants to merge 3 commits into
mainfrom
test/r10-pr12-checkout-order-notify-characterization
Open

cipherwebllc wants to merge 3 commits into
mainfrom
test/r10-pr12-checkout-order-notify-characterization

Conversation

@cipherwebllc

@cipherwebllc cipherwebllc commented Nov 13, 2026 •

Copy link
Copy Markdown
Owner

何をするか

第 10 回レビューの Phase 3 の 1 本目 (PR-12・F-01 と F-02 の前段)。テストだけの PR で、production のコードは 1 字も変えていません。

この後の PR-13 (受注通知の状態と effect を hooks/useCheckoutOrderNotify.ts へ出す) と PR-14 (自社の notify URL の解析を 1 つの関数にする) が、このテストを 1 字も変えずに緑のまま通せるように、いまの components/CheckoutForm.tsx の受注通知の動きを characterization test で固定します。

  • 足した file: tests/components/CheckoutForm.orderNotify.characterization.test.tsx (test 350 個・単独で約 17 秒)
  • 見ているのは、描かれた DOM (文・role・リンク・ボタン) と外への呼び出し (fetch・sessionStorage・window.location.assign・logger) だけです。部品の中の state や関数の名前には触れていません。

レビューへの対応 (2 つ目・3 つ目の commit)

Codex のレビューの MUST 3 件・SHOULD 2 件に対応しました。テストと、この本文だけを直しています (production のコードは変えていません)。

  1. digest を待たない flake (MUST): 経路 A・D の失敗の記録 (logger.warn) は、宛先の hash の crypto.subtle.digest を待ってから出ます (lib/telemetryRedaction.ts:93・components/CheckoutForm.tsx:914・:924・:1199・:1209)。digest は偽の時計と関係なく終わるので、時計を進めるだけの待ち方では、digest が遅い環境で空振りします。digest の呼び出しを境界で数え (中身は実物のまま)、settle() と afterEach が終わりを待つ形にしました。待つ所を 1 か所 (settle()) に寄せたので、記録を見る test にも、見ないが後から記録が出る test にも効きます。同じ形はレビューの 2 本だけではありませんでした: digest を 250 ms 遅らせると、直す前は 342 個のうち 68 個が落ちました (経路 A・D の応答の表が 55 個・D の後に A が続く test が 3 個・記録の中身の test が 4 個。残りの 6 個は経路 C・B の test で、前の test の遅れた記録が次の test にまたがって落ちたもの)。直した後は、同じ遅い digest で 350 個すべて通ります (下の「変異で落ちることの確認」)。
  2. unmount の後の応答 (MUST): 「見える差が出ない」は言い過ぎでした。経路 C は unmount の後でも、控えの受領の記録 (:1039-1040)・terminal への書き換え (:1055-1056)・メモの削除 (:1046) を行います。経路 C に 3 行 (登録できた・打ち切り・再試行できる)、経路 B に 1 本の test を足し、本文の「固定できなかった動き」を直しました。
  3. 経路 D の完了の書き方 (MUST): 「A・D は失敗してもお客様には完了が出る」は D については不正確でした。下の 8 を直しました。
  4. success_url への移動が 3 秒であること (SHOULD): 経路 A に「2,999 ms ではまだ・3,000 ms で移る」の test を足し、経路 C の test も「登録からちょうど 3 秒」にしました。
  5. 額が送信した時点の値であること (SHOULD): 経路 A・D に、確定と同じ描画で会計の額が変わる test を足しました (額の 4 つの欄は押した時点の値のまま)。経路 A には、ガス代の見積もりが確定までに変わる test (USDC・お客様の負担) も足しました。

2 回目のレビュー (3 つ目の commit)

  1. digest の待ちに期限を付け、片づけを無条件にした (MUST): 上の 1 で足した待ちは、終わらない digest があると止まり、afterEach が timeout して location・timer・spy の復元の前で終わっていました。数えていた digest も残るので、関係のない次の test まで落ちていました。待ちに期限 (本物の時計で 2 秒・test の既定の timeout 5 秒より短い) を付け、期限までに終わらない digest があれば、その test を「crypto.subtle.digest が 2000 ms で終わらなかった (N 件)」の文で落とします (黙って先へ進みません)。数えていた分はそこで捨てます。afterEach の片づけは try / finally で必ず走ります。確認の結果は下の「変異で落ちることの確認」の表です。

洗い出した経路

受注通知を送る経路 = 4 つ (計画の「4 経路」と同じ)

経路 いつ 場所 宛先 本文
A 完了の通知 この画面で送った支払い (Pimlico の gasless・通常決済) が確定 components/CheckoutForm.tsx:801-977 (送信 :891-902) URL の webhook の文字列そのまま その場で組む (:855-890)
B 前の会計の注文 hook が返す backgroundOrderDeliveries に確定済みの控えがある :981-1018 (送信 :999-1001) 保存した handle から組む /api/order/notify?h=<handle> 保存した notifyBody そのまま
C 束縛した注文 relay で払った注文 (orderDelivery) が確定 :1022-1071 (送信 :1032-1035) 同上 同上
D 部分の通知 通常決済で、お店への送金だけ確定し利用料がまだ :1076-1252 (送信 :1177-1187) URL の webhook の文字列そのまま その場で組む (:1152-1176・feeUncollected: true)

4 つとも postOrderNotify (:141-159) を通り、409 で本文が { error: 'processing' } のときだけ、同じ本文を 250ms → 1s → 4s → 15s → 60s → 60s の間隔で送り直します (:123-125)。

計画 (verified.md の F-01) は「setOrderNotifyTooOld を呼ぶ effect は 3 つ確かめた・4 つ目は実装の前に特定する」としていました。数え直した結果: 通知を送る effect は 4 つ、そのうち orderNotifyTooOld に触るのは A・C・D の 3 つだけです。4 つ目の B は orderNotifyTooOld を使わず、自分の state (previousOrderNotice) で小さな案内 (role=status) を出します。

「自社の notify URL か」の判定 = CheckoutForm の中に 5 か所 (実装は 3 つ) + lib に 1 つ

計画の「3 経路」は、強さの違う実装の数としては合っています。使っている所は 5 か所で、ほかに lib の照合が 1 つあります。

# 場所 実装 何に使うか
1 :445-446 isOwnOrderNotifyUrl (:161-169・origin と path) + JPYC 払う前の下限の守り (sendsOrderNotify)
2 :827 isOwnOrderNotifyUrl 経路 A で送るか
3 :2152 isOwnOrderNotifyUrl + relay 控えを読めなかったときの警告を出すか
4 :1099-1112 その場の判定 (origin と path。復元した試みでは、h がちょうど 1 つで storeHandle と大文字小文字を除いて同じ) 経路 D で送るか
5 :1448-1450 その場の判定 (origin と path なら、h がちょうど 1 つを求める) relay で払うときに注文を束ねるか
6 lib/orderDeliveryContext.ts:22-25 (CheckoutForm.tsx:268 から呼ぶ) origin と path・h がちょうど 1 つ・正規化した handle が保存した注文と同じ 経路 C で、復元した注文を今の URL の会計のものとするか

経路 B と、この画面で払った経路 C は、URL の webhook を見ません (保存した handle から宛先を組みます)。

固定した表

応答の表 (経路 × 応答)

応答ごとに、fetch の回数と時刻 (最初の送信からの ms)・宛先・method・headers・mode・keepalive・本文、描かれる文と role、sessionStorage、success_url への移動、logger.warn を見ています。

経路 A・D (18 行 × A の 2 経路 + D の 2 経路)。見える結果は 2 つだけです。

応答 送信の時刻 (ms) 結果
200 { ok: true }・200 本文なし・200 { ok: true, bindingConflict: true } 0 そのまま
409 processing が続く 0・250・1250・5250・20250・80250・140250 (7 回) そのまま
409 processing → 200 0・250 そのまま
409 processing ×2 → 503 0・250・1250 そのまま
409 processing → 422 tx_too_old 0・250 スタッフへ
409 JSON でない本文・409 reserved_order 0 そのまま
422 tx_too_old 0 スタッフへ
422 tx_not_found・422 amount_too_low・422 JSON でない本文・400 tx_too_old 0 そのまま
429・503・503 JSON でない本文・通信の失敗 0 そのまま
  • そのまま: 案内なし・大きな完了画面あり・「注文状況を見る」あり・success_url へ 3 秒で移る (notify の応答を待たない)。
  • スタッフへ: 「支払いは確認できましたが、注文を自動登録できませんでした。…」(role=alert)・大きな完了画面を隠す・「注文状況を見る」を隠す・success_url へ移らない。
  • 再試行のボタンは、どの応答でも出ません。429・5xx・通信の失敗は logger.warn だけです。
  • D はまだ完了していないので、完了の画面と success_url への移動はどの応答でもありません。
  • D の後に A が続くとき: 部分の通知 → 完了の通知の順。部分の通知が 409 processing を繰り返す間、完了の通知は待ちます (140.25 秒後・8 回目)。部分の通知が通信の失敗でも完了の通知は送ります。

経路 C (21 行 × 「読み直して復元」と「この画面で払った」)。見える結果は 3 つです。

応答 送信の時刻 (ms) 結果
200 { ok: true }・200 { ok: true, duplicate: true } 0 登録済み
200 { ok: true, bindingConflict: true }・200 { ok: false }・200 本文なし・200 JSON でない本文 0 打ち切り
409 processing が続く 7 回 再試行できる
409 processing → 200 0・250 登録済み
409 processing ×2 → 503 0・250・1250 再試行できる
409 JSON でない本文・409 reserved_order 0 打ち切り
422 tx_too_old・422 order_binding_mismatch・422 JSON でない本文・400 invalid_handle 0 打ち切り
422 tx_not_found・429・503・503 JSON でない本文・通信の失敗 0 再試行できる
503 { bindingConflict: true } 0 打ち切り
  • 登録済み: 案内なし・控え (sessionStorage) を消す・メモを消す・「注文状況を見る」あり。この画面で払った注文は、登録できてから success_url へ移ります (復元した注文は移りません)。
  • 再試行できる: 同じ案内 (role=alert) + 「注文登録を再試行」。控えは notify-pending のまま。押すと同じ本文をもう 1 回送り、送っている間はボタンを消して案内は残します。登録できたら案内が消え、隠れていた大きな完了画面が戻ります。
  • 打ち切り: 同じ案内だけ (ボタンなし)。控えは terminal にします。

経路 B (18 行)。今の会計には触れず、小さな案内 (role=status) だけが変わります。

応答 結果
200 { ok: true }・409 processing → 200 案内なし (控えを消す)
409 processing が続く (7 回)・422 tx_not_found・429・503・503 JSON でない本文・通信の失敗 保留 (「以前の注文の登録が保留中です。このお支払いは続けられます。」・控えは notify-pending)
200 { ok: true, bindingConflict: true }・200 { ok: false }・200 本文なし・200 JSON でない本文・409 JSON でない本文・409 reserved_order・422 tx_too_old・422 JSON でない本文・400 invalid_handle・503 { bindingConflict: true } スタッフへ (「以前の注文を登録できませんでした。…」・控えは terminal)

自社 URL の判定の表 (URL の形 × 判定している所)

18 個の URL の形 × 8 つの見方 (A は gasless と通常決済の 2 つ)。方針が違う所は、違いのまま固定しています。

URL の形 完了 (A) 部分 (D・この画面) 部分 (D・復元) relay の束縛 下限の守り 読込の警告 復元の照合 (C)
h が 1 つ (?h=alice) 送る 送る 送る 束ねる: alice 止める 出す 送る
h が 1 つ + ほかの query 送る 送る 送る 束ねる: alice 止める 出す 送る
h が大文字 (?h=ALICE) 送る 送る 送る 束ねる: alice 止める 出す 送る
h に @ つき (?h=%40alice) 送る 送る 送らない 束ねる: alice 止める 出す 送る
h が別のお店 (?h=bob) 送る 送る 送らない 束ねる: bob 止める 出す 送らない
h なし 送る 送る 送らない 止める: URL が不正 止める 出す 送らない
h が空 (?h=) 送る 送る 送らない 止める: URL が不正 止める 出す 送らない
h が 2 つ (?h=alice&h=bob) 送る 送る 送らない 止める: URL が不正 止める 出す 送らない
h が 2 つ (同じ値) 送る 送る 送らない 止める: URL が不正 止める 出す 送らない
同じ origin・path の末尾に / 送らない 送らない 送らない 束ねない 止めない 出さない 送らない
同じ origin・別の path 送らない 送らない 送らない 束ねない 止めない 出さない 送らない
別の origin・同じ path 送らない 送らない 送らない 束ねない 止めない 出さない 送らない
別の origin・query に path を仕込む 送らない 送らない 送らない 束ねない 止めない 出さない 送らない
同じ host・別の scheme 送らない 送らない 送らない 束ねない 止めない 出さない 送らない
同じ host・別の port 送らない 送らない 送らない 束ねない 止めない 出さない 送らない
相対 URL 送らない 送らない 送らない 止める: 保存できない 止めない 出さない 送らない
URL として読めない 送らない 送らない 送らない 止める: 保存できない 止めない 出さない 送らない
webhook なし 送らない 送らない 送らない 束ねない 止めない 出さない 送らない
  • A と D が送るときの宛先は、URL の文字列そのまま (h が 2 つでも・無くても・大文字でも書き換えない)。C が送るときは、保存した handle から組んだ宛先 (?h=ALICE&utm=x の URL でも ?h=alice へ)。
  • 「束ねない」は、注文を束ねずに relay でそのまま払う (払えます)。「止める」は、案内 (role=alert) を出して署名に進みません。
  • 相対 URL と読めない URL は、lib/url の parse (sanitizeUrl) を通った値では起きない形です。部品の境界の動きとして固定しています。
  • ほかに: 完了の通知は USDC の会計でも送る (下限の守りは JPYC だけ)・読込の警告は relay の経路だけ・B は今の URL を見ない、を別の test で固定しています。

orderNotifyTooOld という名前の state が実際に表すもの

「支払いは確認できましたが、注文を自動登録できませんでした。…」の案内 (role=alert) が出る条件を、経路 × 応答の 21 行で固定しました。

  • 経路 A・D: 422 で本文が JSON の { error: 'tx_too_old' } のときだけ。
  • 経路 C: 登録を確かめられなかった応答すべて (5xx・429・409 processing の出尽くし・通信の失敗・打ち切りの応答)。再試行で登録できれば消えます。
  • 経路 B: 出ません。
  • 出ている間は、大きな完了画面・「注文状況を見る」・success_url の案内と移動を止めます。

名前は too_old ですが、実際は「この画面の注文の登録を確かめられていない」です (PR-13 が orderNotifyFailed に直す予定)。テストは名前に依存していません。

ほかに固定したもの

  • success_url への移動の時刻: 経路 A は確定からちょうど 3 秒後 (秒読みの文は 3 → 2 → 1・2,999 ms ではまだ・3,000 ms で移る)。経路 C (この画面で払った注文) は、登録できてからちょうど 3 秒後。
  • 額は送信した時点の値: 経路 A・D の本文の amount・merchantAmount・feeAmount・customerPays は、払うボタンを押した時点の値です。確定と同じ描画で会計の額が変わっても (3000 → 5000 JPYC)、ガス代の見積もりが変わっても (USDC・0.1 → 0.3)、今の値に置き換えません。
  • unmount の後に届いた応答: 経路 C は、登録できたら控えとメモを消し、打ち切りなら控えを terminal にし、再試行できる応答なら何も変えません。経路 B は、登録できたら控えを消します。
  • 記録の中身: 経路 A・D の logger.warn は status と、宛先の origin・短い hash だけ (URL の query に載った秘密は出さない)。経路 C は status だけ・通信の失敗は理由の符号だけ。経路 B は何も出しません。checkout.success と checkout.order.delivered_fee_uncollected は 1 回ずつ。
  • 同じ描画で 2 つの経路が始まるときの順番: B → C、A → B。

mock の境界

  • mock するもの: wagmi・fetch (vi.spyOn(globalThis, 'fetch'))・時計 (vi.useFakeTimers)・@/lib/logger・支払いの hook (useBatchPayment・useStandardPayment・useJpycEip3009Payment・useSmartAccount・useGasQuote*・useRelayHealth)・経路を決める env 由来の関数 (resolveJpycGaslessProvider・resolveUsdcGaslessProvider・jpycForwarderFor・resolvePaymasterMode)・@/lib/env の 2 つの flag・next/navigation・ConnectButton (stub)・window.location.assign。兄弟の tests/components/CheckoutForm.test.tsx と同じ境界です。
  • 実物のまま: CheckoutForm の中の受注通知の状態・effect・URL の解析、lib/orderDelivery・lib/orderDeliveryContext・lib/orderBind・lib/paymentFlowGuards・lib/paymentRoute、sessionStorage (jsdom)。
  • これから作られる hooks/useCheckoutOrderNotify.ts と lib/order/ownNotifyUrl.ts は、import も mock もしていません。
  • 本物の timer と network は使いません。fetch の応答は本物の Response で、送信の時刻は偽の時計で測っています。
  • crypto.subtle.digest は中身を実物のまま、呼び出しだけを数えています (vi.spyOn で包み、返ってきた promise の終わりを settle() と afterEach が待つ)。偽の時計では進まない本物の非同期を、決まった所で待つためです。待ちには期限 (本物の時計で 2 秒) があり、期限までに終わらなければその test を落とします。PR-13・14 で受注通知を hook や lib へ出しても、digest を呼ぶ所は変わらないので、この境界は変えずに済みます。
  • 兄弟のテストは変えていません。

変異で落ちることの確認

production のコードに 1 か所ずつ変異を入れ、新しいテストが落ちることを確かめて git checkout で戻しました (変異は commit していません)。

  • 入れた変異: 116 個 (応答の表・URL の表の行ごとに 1 つ以上。条件の反転・再試行の回数と間隔・URL の判定の枝・本文の欄・storage の読み書き・描画の条件・秒読みの長さ・額を今の値に置き換える)。
  • 落ちた: 111 個。生き残り: 5 個 (下の「いまのコードで気づいたこと」の 11。どれも別の条件が同じ結果を作っていて、外しても見える動きが変わらない行。Codex も 5 個とも同じ理由で再現しています)。
  • この数字は、レビューへの対応の後のテスト (350 個) に対して、全部を流し直した結果です。
  • レビューで足した変異 (12 個・すべて落ちる): 秒読みを 1 秒にする (経路 A T01・経路 C T02・定数 T03)・4 秒にする (T04)、経路 A の額の 4 つの欄を今の値にする (N01〜N04)、経路 D の同じ 4 つ (N05〜N08)。対応の前は、秒読みを 1 秒にする変異と、経路 A の額を今の値にする変異が生き残っていました (Codex の指摘)。
  • 経緯: 1 つ目の commit の途中 (テスト 335 個・変異 100 個) では 7 個が生き残り、そのうち 2 個はテストの穴だったので、テストを足して落としました (完了の記録の受付番号・部分の通知の from)。
  • 変異の後は毎回 git checkout -- <file> で戻し、git status --porcelain が空であることを確かめています。最後の作業ツリーは clean です。

遅い digest (テスト側の変異): crypto.subtle.digest の完了を、結果はそのままで 250 ms (本物の時計) 遅らせました。

テスト 結果
直す前 (1 つ目の commit) + 遅い digest exit=1 / Tests 68 failed / 274 passed (342) / Duration 16.48s
直した後 (2 つ目の commit) + 遅い digest exit=0 / Tests 350 passed (350) / Duration 53.43s

終わらない digest (テスト側の変異): 経路 A の test 1 つ (「メモは応答が失敗でも消える」) の中だけ、digest が終わらないようにし、関係のない経路 B の test (「確定していない控え (signed) は送らない」) と 2 つ続けて流しました。

テスト 結果
2 つ目の commit (期限なし) + 終わらない digest exit=1 / Tests 2 failed / 348 skipped (350) / Duration 30.74s (2 つとも落ちる: test の timeout 5 秒 → afterEach の timeout 10 秒 → 次の test も timeout)
3 つ目の commit (期限あり) + 終わらない digest exit=1 / Tests 1 failed / 1 passed / 348 skipped (350) / Duration 2.86s (digest を止めた test だけが期限の文で落ち、次の test は通る)
3 つ目の commit + 遅い digest (250 ms) exit=0 / Tests 350 passed (350) / Duration 53.53s

上の 116 個の変異の数字は、2 つ目の commit のテストに対するものです。3 つ目の commit はテストの待ち方だけを変えたので、全部は流し直さず、12 個を抜き取って流し直しました (R01・A03・A11・A20・D05・C03・B05・S01・V01・T01・N04・L01 → 12 個とも落ちる)。

変異の表 (全部)
id 場所 入れた変異 結果 落ちた test の数 落ちた test (先頭)
R01 components/CheckoutForm.tsx:124 再試行の回数を 6 → 5 (最後の 60 秒を外す) KILLED 8 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > Pimlico の gasless > 409 processing が続く → そのまま
R02 components/CheckoutForm.tsx:124 最初の再試行の間隔を 250ms → 500ms KILLED 25 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > Pimlico の gasless > 409 processing が続く → そのまま
R03 components/CheckoutForm.tsx:149 再試行する status を 409 → 429 KILLED 25 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > Pimlico の gasless > 409 processing が続く → そのまま
R04 components/CheckoutForm.tsx:152 409 の本文が processing でなくても再試行する KILLED 14 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > Pimlico の gasless > 409 JSON でない本文 → そのまま
R05 components/CheckoutForm.tsx:173 429 を再試行できる側から外す KILLED 3 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 429 → 再試行できる
R06 components/CheckoutForm.tsx:172 bindingConflict を見ない KILLED 3 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 503 { bindingConflict: true } → 打ち切り
R07 components/CheckoutForm.tsx:174 422 tx_not_found を再試行できる側から外す KILLED 3 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 422 tx_not_found → 再試行できる
R08 components/CheckoutForm.tsx:173 5xx の境界を 500 → 504 KILLED 15 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 409 processing ×2 → 503 → 再試行できる
R09 components/CheckoutForm.tsx:174 409 processing を再試行できる側から外す KILLED 3 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 409 processing が続く → 再試行できる
A01 components/CheckoutForm.tsx:907 tx_too_old の判定から status 422 を外す KILLED 2 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > Pimlico の gasless > 400 tx_too_old (422 ではない) → そのまま
A02 components/CheckoutForm.tsx:908 tx_too_old の判定の文字列を変える KILLED 7 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > Pimlico の gasless > 409 processing → 422 tx_too_old → スタッフへ
A03 components/CheckoutForm.tsx:872 本文から customerPays を落とす KILLED 3 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > 送る本文 (Pimlico の gasless): 送信した時点の額・明細・受付番号・hash・注文状況の token・メモ
A04 components/CheckoutForm.tsx:898 fetch の mode: 'cors' を落とす KILLED 36 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > Pimlico の gasless > 200 { ok: true } → そのまま
A05 components/CheckoutForm.tsx:901 409 processing の再試行をしない KILLED 8 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > Pimlico の gasless > 409 processing が続く → そのまま
A06 components/CheckoutForm.tsx:819 束縛つきの relay でも完了の通知を送る KILLED 26 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > この画面で払った注文 > 200 { ok: true } → 登録済み
A07 components/CheckoutForm.tsx:806 送信の snapshot が無くても進む KILLED 1 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > 送る前に確定を持っていた画面 (払うボタンを押していない) は通知しない
A08 components/CheckoutForm.tsx:807 同じ確定の重複の止めを外す KILLED 2 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > 描き直しても、同じ確定の通知は 1 回だけ
A09 components/CheckoutForm.tsx:911 tx_too_old で案内を出さない KILLED 5 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > Pimlico の gasless > 409 processing → 422 tx_too_old → スタッフへ
A10 components/CheckoutForm.tsx:943 送った後にメモを消さない KILLED 2 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > 送る本文 (Pimlico の gasless): 送信した時点の額・明細・受付番号・hash・注文状況の token・メモ
A11 components/CheckoutForm.tsx:827 自社 URL の判定を外す (webhook があれば送る) KILLED 17 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > この画面で払った注文 > 束ねていない relay の完了 (第三者の webhook) は、通知を送らず、登録を待たずに success_url へ移る
A12 components/CheckoutForm.tsx:876 本文から statusToken を落とす KILLED 34 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > Pimlico の gasless > 200 { ok: true } → そのまま
A13 components/CheckoutForm.tsx:932 部分の通知を待たずに完了の通知を送る KILLED 1 応答の表 — 経路 D: 通常決済の部分の通知 (お店への送金だけ確定・利用料はまだ) > D の後に A が続く順番 (利用料も確定して完了) > 部分の通知が 409 processing を繰り返す間、完了の通知は待つ (140.25 秒後・8 回目)
A14 components/CheckoutForm.tsx:873 本文から orderId を落とす KILLED 39 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > Pimlico の gasless > 200 { ok: true } → そのまま
A15 components/CheckoutForm.tsx:883 本文から hash の欄を落とす KILLED 43 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > Pimlico の gasless > 200 { ok: true } → そのまま
A16 components/CheckoutForm.tsx:880 メモを本文に載せない KILLED 2 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > 送る本文 (Pimlico の gasless): 送信した時点の額・明細・受付番号・hash・注文状況の token・メモ
A17 components/CheckoutForm.tsx:912 tx_too_old で秒読みを止める行を外す (案内は出す) SURVIVED 0
A18 components/CheckoutForm.tsx:814 完了の記録 (checkout.success) の orderId を URL の値だけにする KILLED 1 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > この画面で払った注文 > URL に受付番号が無い注文: 払う前に作った受付番号を、束ねた注文・完了の記録・完了の欄で使う
A19 components/CheckoutForm.tsx:899 keepalive を落とす KILLED 36 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > Pimlico の gasless > 200 { ok: true } → そのまま
A20 components/CheckoutForm.tsx:919 失敗の記録 (non_ok) から宛先の hash を落とす KILLED 1 通知の失敗の記録 (logger.warn) の中身 > 経路 A 完了: 応答の status と、宛先の origin・短い hash (URL の全文は出さない)
A21 components/CheckoutForm.tsx:926 通信の失敗の記録から宛先の origin を落とす KILLED 1 通知の失敗の記録 (logger.warn) の中身 > 経路 A 完了: 通信の失敗は、error と宛先の origin・短い hash
U01 components/CheckoutForm.tsx:164 isOwnOrderNotifyUrl: path を見ない KILLED 8 自社 URL の判定の表 > 同じ origin・path の末尾に / > 完了の通知 (経路 A・Pimlico の gasless): 送らない
U02 components/CheckoutForm.tsx:164 isOwnOrderNotifyUrl: origin を見ない KILLED 12 自社 URL の判定の表 > 別の origin・同じ path > 完了の通知 (経路 A・Pimlico の gasless): 送らない
U03 components/CheckoutForm.tsx:166 isOwnOrderNotifyUrl: 読めない URL を自社とみなす KILLED 8 自社 URL の判定の表 > 相対 URL > 完了の通知 (経路 A・Pimlico の gasless): 送らない
U04 components/CheckoutForm.tsx:446 下限の守り: 自社 URL の判定を外す KILLED 8 自社 URL の判定の表 > 同じ origin・path の末尾に / > 払う前の下限の守り (お店に 0.99 JPYC): 止めない
U05 components/CheckoutForm.tsx:2152 読込の警告: 自社 URL の判定を外す KILLED 8 自社 URL の判定の表 > 同じ origin・path の末尾に / > 控えを読めなかったときの警告 (relay): 出さない
U06 components/CheckoutForm.tsx:2152 読込の警告: relay の条件を外す KILLED 1 自社 URL の判定の表 > 控えを読めなかったときの警告は relay の経路だけ: 自社の URL でも通常決済・Pimlico の gasless では出さない
D01 components/CheckoutForm.tsx:1083 利用料 0 でも部分の通知を送る KILLED 1 応答の表 — 経路 D: 通常決済の部分の通知 (お店への送金だけ確定・利用料はまだ) > 利用料が 0 の通常決済は部分の通知を送らない
D02 components/CheckoutForm.tsx:1107 復元: h が 1 つの条件を外す KILLED 2 自社 URL の判定の表 > h が 2 つ > 部分の通知 (経路 D・読み直して復元した): 送らない
D03 components/CheckoutForm.tsx:1105 この画面で払った場合も handle の一致を求める KILLED 6 自社 URL の判定の表 > h に @ つき > 部分の通知 (経路 D・この画面で払った): 送る
D04 components/CheckoutForm.tsx:1108 復元: handle の比較で大文字小文字を区別する KILLED 1 自社 URL の判定の表 > h が大文字 > 部分の通知 (経路 D・読み直して復元した): 送る
D05 components/CheckoutForm.tsx:1174 本文から feeUncollected を落とす KILLED 42 応答の表 — 経路 D: 通常決済の部分の通知 (お店への送金だけ確定・利用料はまだ) > この画面で払った > 200 { ok: true } → そのまま
D06 components/CheckoutForm.tsx:1115 重複の止めの key から利用料の hash を外す KILLED 1 応答の表 — 経路 D: 通常決済の部分の通知 (お店への送金だけ確定・利用料はまだ) > 利用料の送金の hash が分かったら、同じ注文をもう 1 回 (hash つきで) 送る。同じ状態の描き直しでは送らない
D07 components/CheckoutForm.tsx:1103 origin を見ない KILLED 6 自社 URL の判定の表 > 別の origin・同じ path > 部分の通知 (経路 D・この画面で払った): 送らない
D08 components/CheckoutForm.tsx:1103 path を見ない KILLED 4 自社 URL の判定の表 > 同じ origin・path の末尾に / > 部分の通知 (経路 D・この画面で払った): 送らない
D09 components/CheckoutForm.tsx:1193 tx_too_old の判定の文字列を変える KILLED 8 応答の表 — 経路 D: 通常決済の部分の通知 (お店への送金だけ確定・利用料はまだ) > この画面で払った > 409 processing → 422 tx_too_old → スタッフへ
D10 components/CheckoutForm.tsx:1192 tx_too_old の判定から status 422 を外す KILLED 2 応答の表 — 経路 D: 通常決済の部分の通知 (お店への送金だけ確定・利用料はまだ) > この画面で払った > 400 tx_too_old (422 ではない) → そのまま
D11 components/CheckoutForm.tsx:1216 完了の通知に渡す promise を残さない KILLED 1 応答の表 — 経路 D: 通常決済の部分の通知 (お店への送金だけ確定・利用料はまだ) > D の後に A が続く順番 (利用料も確定して完了) > 部分の通知が 409 processing を繰り返す間、完了の通知は待つ (140.25 秒後・8 回目)
D12 components/CheckoutForm.tsx:1106 復元: お店の handle が無くても送る SURVIVED 0
D13 components/CheckoutForm.tsx:1221 送った後にメモを消さない KILLED 3 応答の表 — 経路 D: 通常決済の部分の通知 (お店への送金だけ確定・利用料はまだ) > 送る本文: 通常決済の形 + お店への送金の hash と block + feeUncollected
D14 components/CheckoutForm.tsx:1156 本文の from を今の接続先にする (復元した送り主を使わない) KILLED 18 応答の表 — 経路 D: 通常決済の部分の通知 (お店への送金だけ確定・利用料はまだ) > 読み直して復元した > 200 { ok: true } → そのまま
D15 components/CheckoutForm.tsx:1186 409 processing の再試行をしない KILLED 9 応答の表 — 経路 D: 通常決済の部分の通知 (お店への送金だけ確定・利用料はまだ) > この画面で払った > 409 processing が続く → そのまま
D16 components/CheckoutForm.tsx:1172 利用料の hash を本文に載せない KILLED 1 応答の表 — 経路 D: 通常決済の部分の通知 (お店への送金だけ確定・利用料はまだ) > 利用料の送金の hash が分かったら、同じ注文をもう 1 回 (hash つきで) 送る。同じ状態の描き直しでは送らない
D17 components/CheckoutForm.tsx:1201 失敗の記録 (non_ok) から宛先の origin を落とす KILLED 1 通知の失敗の記録 (logger.warn) の中身 > 経路 D 部分: 応答の status と、宛先の origin・短い hash (URL の全文は出さない)
C01 components/CheckoutForm.tsx:1038 登録済みの判定を res.ok だけにする KILLED 8 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 200 { ok: true, bindingConflict: true } → 打ち切り
C02 components/CheckoutForm.tsx:1042 登録できても案内を消さない KILLED 2 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 「注文登録を再試行」: 同じ本文をもう 1 回送り、送っている間はボタンを消して案内は残す。登録できたら案内も消す
C03 components/CheckoutForm.tsx:1062 打ち切りでも再試行のボタンを出す KILLED 24 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 200 { ok: true, bindingConflict: true } → 打ち切り
C04 components/CheckoutForm.tsx:1068 通信の失敗で再試行のボタンを出さない KILLED 3 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 通信の失敗 → 再試行できる
C05 components/CheckoutForm.tsx:1050 復元した注文でも success_url へ移る SURVIVED 0
C06 components/CheckoutForm.tsx:1031 宛先に h を付けない KILLED 51 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 200 { ok: true } → 登録済み
C07 components/CheckoutForm.tsx:1025 確定の hash と控えの hash の一致を見ない KILLED 1 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 確定の hash が控えの hash と違えば送らない
C08 components/CheckoutForm.tsx:1023 復元でも今の URL との照合を通さない KILLED 14 自社 URL の判定の表 > h が別のお店 > 復元した束縛つきの注文の照合 (経路 C・保存した handle = alice): 送らない
C09 components/CheckoutForm.tsx:1046 登録できてもメモを消さない KILLED 7 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 200 { ok: true } → 登録済み
C10 components/CheckoutForm.tsx:1054 打ち切りでも控えを terminal にしない KILLED 24 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 200 { ok: true, bindingConflict: true } → 打ち切り
C11 components/CheckoutForm.tsx:1026 再試行の回数を key に入れない (ボタンを押しても送らない) KILLED 5 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 「注文登録を再試行」: 同じ本文をもう 1 回送り、送っている間はボタンを消して案内は残す。登録できたら案内も消す
C12 components/CheckoutForm.tsx:1028 送り始めに再試行のボタンを消さない KILLED 2 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 「注文登録を再試行」: 同じ本文をもう 1 回送り、送っている間はボタンを消して案内は残す。登録できたら案内も消す
C13 components/CheckoutForm.tsx:1060 登録を確かめられない応答で案内を出さない KILLED 44 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 200 { ok: true, bindingConflict: true } → 打ち切り
C14 components/CheckoutForm.tsx:1066 通信の失敗で案内を出さない KILLED 5 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 通信の失敗 → 再試行できる
C15 components/CheckoutForm.tsx:1044 登録できても注文状況の token を置かない KILLED 10 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 200 { ok: true } → 登録済み
C16 components/CheckoutForm.tsx:1040 登録できても控えを消さない KILLED 16 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 200 { ok: true } → 登録済み
C17 components/CheckoutForm.tsx:1027 重複の止めを外す KILLED 1 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > この画面で払った注文 > この画面で束ねた注文は、完了の通知 (経路 A) を別に送らない。完了の記録は 1 回で、描き直しても送り直さない
C18 components/CheckoutForm.tsx:1041 登録できたことを覚えない (success_url へ進めない) KILLED 5 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > この画面で払った注文 > 200 { ok: true } → 登録済み
C19 components/CheckoutForm.tsx:1034 保存した本文でなく空の本文を送る KILLED 48 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 200 { ok: true } → 登録済み
C20 components/CheckoutForm.tsx:1034 409 processing の再試行をしない KILLED 6 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 409 processing が続く → 再試行できる
C21 components/CheckoutForm.tsx:1063 非 ack の記録 (checkout.webhook.non_ok) を出さない KILLED 35 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 200 { ok: true, bindingConflict: true } → 打ち切り
C22 components/CheckoutForm.tsx:1069 通信の失敗の記録から理由の符号を落とす KILLED 1 通知の失敗の記録 (logger.warn) の中身 > 経路 C: 応答の status だけ。通信の失敗は理由の符号だけ。経路 B は何も出さない
B01 components/CheckoutForm.tsx:993 確定していない控えも送る KILLED 1 応答の表 — 経路 B: 前の会計の未登録の注文の通知 > 確定していない控え (signed) は送らない
B02 components/CheckoutForm.tsx:995 同じ注文の重複の止めを外す KILLED 1 応答の表 — 経路 B: 前の会計の未登録の注文の通知 > 前の注文が 2 件: handle ごとの宛先へ 1 回ずつ送り、1 件でも「スタッフへ」ならその案内を出す
B03 components/CheckoutForm.tsx:1005 登録済みの判定を res.ok だけにする KILLED 4 応答の表 — 経路 B: 前の会計の未登録の注文の通知 > 200 { ok: true, bindingConflict: true } → スタッフへ
B04 components/CheckoutForm.tsx:1006 控えを消せなくても「案内なし」にする KILLED 1 控え (sessionStorage) を更新できないとき > 経路 B: 登録できたが控えを消せない → 「保留」の案内を残す
B05 components/CheckoutForm.tsx:1007 再試行できる応答でも「スタッフへ」にする KILLED 7 応答の表 — 経路 B: 前の会計の未登録の注文の通知 > 409 processing が続く → 保留
B06 components/CheckoutForm.tsx:988 案内の優先を逆にする (保留 > スタッフへ) KILLED 1 応答の表 — 経路 B: 前の会計の未登録の注文の通知 > 前の注文が 2 件: handle ごとの宛先へ 1 回ずつ送り、1 件でも「スタッフへ」ならその案内を出す
B07 components/CheckoutForm.tsx:1008 打ち切りでも控えを terminal にしない KILLED 11 応答の表 — 経路 B: 前の会計の未登録の注文の通知 > 200 { ok: true, bindingConflict: true } → スタッフへ
B08 components/CheckoutForm.tsx:2137 同じお店の確認中でも「保留」を出す KILLED 1 応答の表 — 経路 B: 前の会計の未登録の注文の通知 > 同じお店の前の支払いを確認中 × 503 (保留) → 保留 0 件・スタッフへ 0 件
B09 components/CheckoutForm.tsx:998 宛先に h を付けない KILLED 22 同じ描画で 2 つの経路が始まるときの順番 > 前の注文 (経路 B) → 復元した束縛つきの注文 (経路 C)
B10 components/CheckoutForm.tsx:1000 409 processing の再試行をしない KILLED 2 応答の表 — 経路 B: 前の会計の未登録の注文の通知 > 409 processing が続く → 保留
B11 components/CheckoutForm.tsx:996 送り始めに「保留」を置かない KILLED 8 応答の表 — 経路 B: 前の会計の未登録の注文の通知 > 409 processing が続く → 保留
B12 components/CheckoutForm.tsx:1013 通信の失敗の後に案内を更新しない SURVIVED 0
S01 components/CheckoutForm.tsx:1450 h が 1 つの条件を外す KILLED 2 自社 URL の判定の表 > h が 2 つ > relay で払うときの束縛: 止める: URL が不正
S02 components/CheckoutForm.tsx:1449 origin を見ない KILLED 3 自社 URL の判定の表 > 別の origin・同じ path > relay で払うときの束縛: 束ねない
S03 components/CheckoutForm.tsx:1449 path を見ない KILLED 2 自社 URL の判定の表 > 同じ origin・path の末尾に / > relay で払うときの束縛: 束ねない
S04 components/CheckoutForm.tsx:1465 order_binding_mismatch を「保存できない」の案内にする KILLED 1 自社 URL の判定の表 > h が空 > relay で払うときの束縛: 止める: URL が不正
S05 components/CheckoutForm.tsx:1459 メモを注文に入れない KILLED 1 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > この画面で払った注文 > 払う前に注文を束ねる: 受付 (admission) → 署名。受付番号・handle・メモ・注文状況の token を署名の前に固定する
S06 components/CheckoutForm.tsx:1461 束ねた注文を控えない (boundOrderRef を置かない) KILLED 26 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > この画面で払った注文 > 200 { ok: true } → 登録済み
V01 components/CheckoutForm.tsx:2263 案内が出ていても大きな完了画面を隠さない KILLED 28 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > Pimlico の gasless > 409 processing → 422 tx_too_old → スタッフへ
V02 components/CheckoutForm.tsx:2206 案内が出ていても「注文状況を見る」を出す KILLED 6 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > Pimlico の gasless > 409 processing → 422 tx_too_old → スタッフへ
V03 components/CheckoutForm.tsx:2218 案内が出ていても success_url の案内を出す KILLED 6 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > Pimlico の gasless > 409 processing → 422 tx_too_old → スタッフへ
V04 components/CheckoutForm.tsx:1364 案内が出ていても success_url へ移る (doRedirect) KILLED 1 応答の表 — 経路 D: 通常決済の部分の通知 (お店への送金だけ確定・利用料はまだ) > D の後に A が続く順番 (利用料も確定して完了) > 部分の通知が 422 tx_too_old なら、完了しても案内は残り、大きな完了画面と success_url への移動は無い
V05 components/CheckoutForm.tsx:2219 束縛つきの注文で、登録の前でも success_url の案内を出す KILLED 1 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > この画面で払った注文 > 応答を待つ間: 大きな完了画面は出ているが、success_url への案内と移動は無い。登録できてから秒読みが始まる
V06 components/CheckoutForm.tsx:1364 束縛つきの注文で、登録の前でも doRedirect を通す SURVIVED 0
V07 components/CheckoutForm.tsx:2157 再試行のボタンを出さない KILLED 19 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > 読み直して復元した注文 > 409 processing が続く → 再試行できる
V08 components/CheckoutForm.tsx:2162 案内の role=alert を外す KILLED 60 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > Pimlico の gasless > 409 processing → 422 tx_too_old → スタッフへ
L01 lib/orderDeliveryContext.ts:26 復元の照合: h が 1 つの条件を外す KILLED 2 自社 URL の判定の表 > h が 2 つ > 復元した束縛つきの注文の照合 (経路 C・保存した handle = alice): 送らない
L02 lib/orderDeliveryContext.ts:26 復元の照合: handle を正規化しない KILLED 2 自社 URL の判定の表 > h が大文字 > 復元した束縛つきの注文の照合 (経路 C・保存した handle = alice): 送る
L03 lib/orderDeliveryContext.ts:25 復元の照合: path を見ない KILLED 2 自社 URL の判定の表 > 同じ origin・path の末尾に / > 復元した束縛つきの注文の照合 (経路 C・保存した handle = alice): 送らない
T01 components/CheckoutForm.tsx:954 経路 A の秒読みを 3 秒 → 1 秒 KILLED 1 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > success_url へ移るのは、確定からちょうど 3 秒後 (notify の応答を待たない)
T02 components/CheckoutForm.tsx:1050 経路 C の秒読みを 3 秒 → 1 秒 KILLED 1 応答の表 — 経路 C: 束縛した注文 (relay) の通知 > この画面で払った注文 > 応答を待つ間: 大きな完了画面は出ているが、success_url への案内と移動は無い。登録できてから秒読みが始まる
T03 components/CheckoutForm.tsx:120 秒読みの定数を 3000 → 1000 KILLED 2 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > success_url へ移るのは、確定からちょうど 3 秒後 (notify の応答を待たない)
T04 components/CheckoutForm.tsx:120 秒読みの定数を 3000 → 4000 KILLED 2 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > success_url へ移るのは、確定からちょうど 3 秒後 (notify の応答を待たない)
N01 components/CheckoutForm.tsx:866 経路 A: amount を今の合計にする KILLED 1 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > 額の 4 つの欄は払うボタンを押した時点の値: 確定と同じ描画で会計の額が変わっても、押した時点の額を送る
N02 components/CheckoutForm.tsx:870 経路 A: merchantAmount を今の値にする KILLED 1 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > 額の 4 つの欄は払うボタンを押した時点の値: 確定と同じ描画で会計の額が変わっても、押した時点の額を送る
N03 components/CheckoutForm.tsx:871 経路 A: feeAmount を今の値にする KILLED 1 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > 額の 4 つの欄は払うボタンを押した時点の値: 確定と同じ描画で会計の額が変わっても、押した時点の額を送る
N04 components/CheckoutForm.tsx:872 経路 A: customerPays を今の値にする KILLED 2 応答の表 — 経路 A: この画面で送った支払いの完了の通知 > 額の 4 つの欄は払うボタンを押した時点の値: 確定と同じ描画で会計の額が変わっても、押した時点の額を送る
N05 components/CheckoutForm.tsx:1160 経路 D: amount を今の合計にする KILLED 1 応答の表 — 経路 D: 通常決済の部分の通知 (お店への送金だけ確定・利用料はまだ) > 額の 4 つの欄は払うボタンを押した時点の値: お店への送金の確定と同じ描画で会計の額が変わっても、押した時点の額を送る
N06 components/CheckoutForm.tsx:1163 経路 D: merchantAmount を今の値にする KILLED 1 応答の表 — 経路 D: 通常決済の部分の通知 (お店への送金だけ確定・利用料はまだ) > 額の 4 つの欄は払うボタンを押した時点の値: お店への送金の確定と同じ描画で会計の額が変わっても、押した時点の額を送る
N07 components/CheckoutForm.tsx:1164 経路 D: feeAmount を今の値にする KILLED 1 応答の表 — 経路 D: 通常決済の部分の通知 (お店への送金だけ確定・利用料はまだ) > 額の 4 つの欄は払うボタンを押した時点の値: お店への送金の確定と同じ描画で会計の額が変わっても、押した時点の額を送る
N08 components/CheckoutForm.tsx:1165 経路 D: customerPays を今の値にする KILLED 1 応答の表 — 経路 D: 通常決済の部分の通知 (お店への送金だけ確定・利用料はまだ) > 額の 4 つの欄は払うボタンを押した時点の値: お店への送金の確定と同じ描画で会計の額が変わっても、押した時点の額を送る

いまのコードで気づいたこと (直していません。PR-13・14 への申し送り)

どれも、いまの動きのままテストに固定してあります。直すかどうかは PR-13・14 かその後で決めてください。行は components/CheckoutForm.tsx (main 8c1a317)。

所によって方針が違う (F-02 の中身)

  1. h が 2 つ・h なし・h が空: 経路 A (:827) と、この画面で払った経路 D (:1102-1105) は URL の文字列そのままへ送ります。relay の束縛 (:1450) は「Checkout URL が不正です」で止めます。同じリンクが、relay なら払えず、Pimlico の gasless・通常決済なら払えて notify も送ります。
  2. h に @ つき (?h=%40alice): relay の束縛と経路 C の照合は @ を外して alice と読みます。経路 D の復元 (:1108-1109) は小文字にするだけなので一致せず、読み直した後の部分の通知を送りません。
  3. h が別のお店 (?h=bob・storeHandle は alice): relay の束縛は URL の h (bob) で注文を束ねます (:1458・params.storeHandle と照らしません)。storeHandle と照らすのは経路 D の復元だけです。経路 D も、この画面で払ったときは storeHandle を見ません (:1105)。
  4. 通貨: 払う前の下限の守り (:445-446) は JPYC の会計だけですが、経路 A (:827) は通貨を見ません。USDC の会計でも、自社の URL なら notify を送ります。
  5. 相対 URL・読めない URL + relay: new URL が投げ、:1463-1467 の catch が「注文の復元情報をこのブラウザに保存できないため…」(保存の文) を出して止めます。理由と文が合っていません。lib/url の sanitizeUrl を通った値では起きません。
  6. fetch の形: 経路 A・D は mode: 'cors' を付け、経路 B・C は付けません (同一 origin なので結果は同じ)。

バグの疑い

  1. お客様のメモが届かないことがある: メモ (sessionStorage の openpay:order-memo:<受付番号>) は、送った直後に応答を待たずに消します (:943-951・:1221-1229)。通常決済で、部分の通知 (D) が通信の失敗 → 完了の通知 (A) は成功、のとき、メモは D と一緒に消えていて A の本文に載りません。D がサーバーに届いていないので、メモをお店へ届ける道が残りません (サーバー側の扱いはこの PR では見ていません。client が送る本文だけの話です)。経路 A だけのときも、失敗した通知と一緒に消えます (送り直しはありません)。
  2. 経路 A・D は 429・5xx・通信の失敗で、通知の失敗を画面に出さない: logger.warn だけで、案内も再試行のボタンも出ません。経路 A では、お客様にはそのまま完了が出ます。経路 D だけではまだ完了になりません (利用料の送信が残っていて、画面は利用料の送信中・再試行の欄のまま)。利用料が後で成功したときに完了が出ます。どちらの場合も、お店に注文が届いていないことはお客様に伝わりません。経路 C は同じ応答で案内と「注文登録を再試行」を出します。コメント (:826) のとおりの設計ですが、経路で差があります。「注文状況を見る」も、経路 A では notify の応答を待たずに出ます (:840-843)。503 のときは、リンク先の token がサーバーに無い見込みです。
  3. success_url への移動が、経路 A の再試行を待たない: 移動は確定の 3 秒後で、409 processing の再試行は 250ms・1.25 秒・5.25 秒・… と続きます。テストは画面が残るので 7 回と固定していますが、実機では移動の後の再試行は走らない見込みです (keepalive が効くのは送信済みの 1 回)。

読まれない state・二重の守り (PR-13 で動かすときの注意)

  1. 経路 C の setBindingStorageError(true) (:1040・:1056) は画面に出ません。案内 (:2142) は !relay.data?.success を求め、経路 C は relay.data.success が前提だからです。控えを消せない・terminal に書けないときも、見える動きは変わりません (テストで固定)。
  2. 外しても見える動きが変わらない行 (変異で生き残り): :912 (A の tx_too_old での setRedirectIn(null))・:1050 の !relay.restoredIntent・:1364-1365 の boundOrderRef.current && !boundNotifyAcknowledged・:1013 の catch の updateNotice()・:1106 の !!params.storeHandle (外すと undefined.toLowerCase() が try の中で投げて、同じ「送らない」になる)。別の条件が同じ結果を作っています。このテストでは守れていない行なので、PR-13 で触るなら目で確かめてください。
  3. 経路 B は、登録できても前の注文のメモを消しません (経路 C は消す :1046)。タブを閉じるまで残ります。
  4. 名前 (F-01): orderNotifyTooOld (state :185・messages の key も同じ) は、経路 A・D では名前どおり (422 tx_too_old)、経路 C では「登録を確かめられていない」(:1060・:1066) です。

固定できなかった動き

  • 経路 C の bindingStorageError: 上の 10 のとおり画面に出ないので、「出ない」ことだけを固定しています。
  • 経路 B の unmount の守りそのもの (:986 の previousNotificationMountedRef): unmount の後は案内 (state) を更新しない、という守りです。React は unmount の後の更新を捨てるので、守りを外しても DOM にも外への呼び出しにも差が出ず、テストにできません。unmount の後に届いた応答で 外に残る記録 (経路 C・B の sessionStorage の控えとメモ) の方は、テストで固定しました (上の「ほかに固定したもの」)。
  • 実機の keepalive と、移動の後の再試行 (上の 9)。jsdom では画面が残ります。
  • 注文状況の token の値: 乱数なので、形 (isOrderTokenLike) と「本文・リンク・sessionStorage で同じ値」だけを見ています。
  • hook がいつ orderDelivery・backgroundOrderDeliveries を返すか: 支払いの hook は境界として mock しています。実物の hook との組み合わせは、兄弟の CheckoutForm.test.tsx の A2c saved-order-only notification が見ています。
  • StrictMode の二重の effect (本番では起きません)。
  • 生き残った変異の行 (上の 11)。

この PR が変えないもの

  • production のコード (components/・hooks/・lib/・app/)・公開文言・env・依存・既存のテスト。差分は新しい test file 1 つだけです。
  • bundle (test だけなので測っていません)。

検証

  • ./node_modules/.bin/tsc --noEmit: exit 0
  • ./node_modules/.bin/eslint tests/components/CheckoutForm.orderNotify.characterization.test.tsx: exit 0 (指摘なし)
  • 新しい file だけ: Test Files 1 passed (1)・Tests 350 passed (350)・exit 0・17.12 秒 (期限を付ける前は 17.03 秒)
  • full vitest: Test Files 927 passed (927)・Tests 25971 passed (25971)・exit 0・88.50 秒 (終わったときの load average 14.46。新しい file は並列の負荷の中でも通りました)
  • 遅い digest (250 ms) でも新しい file は 350 個すべて通る。終わらない digest は、その test だけが期限の文で落ちる (上の表)。
  • page も production のコードも変えていないので、next build と bundle の予算は流していません。
  • Prettier は使っていません。依存は足していません。npm ci / npm install は実行していません。

🤖 Generated with Claude Code

https://claude.ai/code/session_01HeizmagJBgL5peL5mQpxkc

第 10 回レビューの F-01・F-02 の前段。この後の PR-13 (受注通知の状態と effect を hook へ出す) と PR-14 (自社の notify URL の解析を 1 つにする) が、このテストを 1 字も変えずに通せるように、いまの components/CheckoutForm.tsx の受注通知の動きを characterization test で固定する。production のコードは変えない。

固定したもの
- 応答の表。経路 A (完了の通知)・B (前の会計の注文)・C (束縛した注文)・D (通常決済の部分の通知) のそれぞれに、200・409 processing・422・429・5xx・JSON でない本文・通信の失敗などを返し、fetch の回数・時刻 (再試行の間隔)・宛先・method・本文と、描かれる文・role・リンク・ボタン、sessionStorage の控えとメモ、success_url への移動、logger の記録を見る。
- 自社 URL の判定の表。判定している所ごとに、h が 1 つ・無い・2 つ・空・大文字・@ つき・別のお店・別の origin・別の path・読めない URL などで、送るか・どこへ送るか・何を出すかを見る。所によって方針が違う所は、違いのまま書く。
- 「自動登録できませんでした」の案内 (state の名前は orderNotifyTooOld) が出る条件。経路 A・D では 422 tx_too_old のときだけ、経路 C では登録を確かめられなかった応答すべてで出て、登録できれば消える。

mock は境界だけ (wagmi・fetch・時計・logger・支払いの hook・経路を決める関数)。受注通知の状態・effect・URL の解析と、lib/orderDelivery・lib/orderBind は実物。時計は偽物で、本物の timer と network は使わない。

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeizmagJBgL5peL5mQpxkc
@vercel

vercel Bot commented Nov 13, 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 Nov 13, 2026 5:55pm UTC

Request Review

cipherwebllc and others added 2 commits November 14, 2026 01:46
…ount 後の片づけを固定する

PR-12 のレビューの指摘への対応。production のコードは変えない。

- 経路 A・D の失敗の記録 (logger.warn) は、宛先の hash の WebCrypto の digest を待ってから出る。digest は偽の時計と関係なく終わるので、時計を進めるだけの待ち方では、digest が遅い環境で記録を見る test が空振りし、遅れた記録が次の test にまたがる。digest の呼び出しを境界で数え (中身は実物)、settle と afterEach が終わりを待つ形にした。記録を見る test・見ない test の両方に効く。
- 経路 C・B で、応答を待つ間に画面を離れた (unmount) 後に応答が届いたときの、sessionStorage の控えとメモの片づけを足した。
- 経路 A の success_url への移動が、確定からちょうど 3 秒後 (2,999 ms ではまだ・3,000 ms で移る) であることを足した。経路 C も登録からちょうど 3 秒後にした。
- 経路 A・D の額の 4 つの欄が、払うボタンを押した時点の値であることを足した (確定と同じ描画で会計の額やガス代の見積もりが変わっても、今の値に置き換えない)。

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeizmagJBgL5peL5mQpxkc
PR-12 のレビュー (2 回目) の指摘への対応。production のコードは変えない。

- digest の終わりを待つ helper は、終わらない digest があると止まり、afterEach が timeout して、location・timer・spy の復元の前で終わっていた。数えていた digest も残るので、関係のない次の test まで落ちていた。
- 待ちに期限 (本物の時計で 2 秒) を付けた。期限までに終わらない digest があれば、その test を「crypto.subtle.digest が 2000 ms で終わらなかった」の文で落とし、数えていた分は捨てる。黙って先へは進まない。event loop が止まっていただけの場合に誤って落とさないよう、期限の後に 1 巡だけ流してから数える。
- afterEach の片づけ (数えていた digest を空にする・location・timer・spy の復元) を try / finally で無条件にした。

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeizmagJBgL5peL5mQpxkc

This branch has not been deployed

No deployments
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