質問
データ API にリクエスト課金型のアクセスを追加する必要があります。未払いのリクエストは HTTP 402 を返し、クライアントは支払い完了後に元のリクエストを再試行する必要があります。RFC 9110 における 402 のステータスを説明した上で、支払い要件、証明の検証、リソースの紐付け、リプレイ保護、冪等性、返金、および突合処理を網羅する x402 のようなエンドツーエンドのプロトコルを設計してください。
面接官が見ているポイント
- 402 の標準化された意味と具体的な決済スキームを区別できているか:RFC 9110 はステータスコードを予約していますが、決済ネットワーク、通貨、レスポンス形式は定義していません。
- 支払いを別のリクエストに流用できないよう、支払い証明をリソース、金額、受取人、ネットワーク、有効期限に紐付けているか。
- クライアントのリトライ、タイムアウト、二重請求、ブロックチェーンの承認遅延、および会計上の整合性を処理できているか。
- 決済サービス、リソースサービス、決済当事者、および監査ログの間の信頼境界を明確に述べられるか。
模範解答
402 は HTTP ステータスコードレジストリで「Payment Required」として登録されています。RFC 9110 はこれを予約していますが、普遍的な決済プロトコルは定義していません。プロトコルは 402 を、すでに支払いが完了したことの証明ではなく、機械可読なチャレンジとして扱う必要があります。
リソースサービスは、402 レスポンスで一意の支払い要件を返すことができます。これにはリソース識別子、メソッドとパス、金額、アセット、ネットワーク、受取人、有効期限、およびノンスを含める必要があります。クライアントはまさにこれらのフィールドに対して署名または支払いを行います。サービスまたは信頼できるファシリテーターが証明を検証し、金額、受取人、ネットワーク、リソースを確認した上で、リソースサービスにワンタイムレシートを発行します。
リソースサービスは、高コストな処理を実行する前に、リクエスト ID と支払い ID の間の冪等な関係を記録する必要があります。同じリクエスト ID でのリトライは、同一の結果または明示的な処理中ステータスを返します。支払いの成功はリソース実行の成功を意味しないため、支払い、認可、実行、返金には追跡可能な状態が必要です。突合バッチ処理により、チェーン上の承認、サービスレコード、および実際の提供内容の間の差異を検出する必要があります。
実装のスケッチ
以下の疑似コードは、コアとなるチャレンジとリトライの境界を示しています。本番システムには決済検証機能、冪等ストレージ、および監査ログも必要です。
handle(request):
id = request.idempotencyKey
if receiptStore.has(id):
return receiptStore.result(id)
requirement = makeRequirement(
resource = canonicalResource(request),
amount = quote(request),
network = "base",
expiresAt = now + 60s,
nonce = randomBytes(16)
)
proof = request.headers["Payment-Proof"]
if proof is missing:
return 402, { "payment-required": requirement }
payment = verifyProof(proof, requirement)
if payment.invalid or payment.expired or payment.replayed:
return 402, { "payment-required": requirement, "reason": "invalid-proof" }
result = executeOnce(id, request, payment)
receiptStore.put(id, payment.id, result)
return 200, result重要な不変条件は、canonicalResource と verifyProof が同じ正規化ルールを使用することです。そうでない場合、1つのリソースが複数の文字列表現を持つ可能性があり、署名検証と認可が一致しなくなる恐れがあります。リトライによって副作用が重複しないように、executeOnce には一意制約、トランザクション、または永続的な状態が必要です。
よくある落とし穴
- 402 自体に決済フローが含まれていると思い込むこと。これは単に支払いが必要であることを示しているだけであり、プロトコルがフィールドと検証ルールを定義する必要があります。
- リソース、ネットワーク、受取人、アセット、有効期限を無視して金額のみをチェックし、リソース間のすり替えやネットワーク間のリプレイを許してしまうこと。
- 冪等なリクエストレコードを作成せずに支払い確認直後に副作用を実行し、タイムアウトによるリトライで二重請求やリソースの二重作成が発生すること。
- 送信されたチェーン上のトランザクションを最終確定した決済として扱うこと。承認の遅延、再編成(reorg)、ファシリテーターの障害、返金はステートマシンで扱うべき事項です。
- 支払い証明をログや URL に含めてしまい、クレデンシャルの漏洩やリプレイリスクを引き起こすこと。
本番環境でのトレードオフ
低額・低リスクな読み取り処理では、短期間有効な見積もり、ワンタイムノンス、および非同期の最終突合で十分な場合があります。高額な書き込み処理では、提供前に検証可能な決済状態を取得し、冪等トランザクションの背後で実行する必要があります。クライアントにウォレットやオンチェーン機能がない場合、ファシリテーターが代理で支払うことができますが、その信頼範囲、手数料、制限、および障害時のフォールバックを明確にする必要があります。
プロトコルには、価格変更、期限切れのチャレンジ、一部支払い、支払い成功後のリソース障害、返金、およびサービス低下に関するルールも必要です。キャッシュは、支払い証明を含むレスポンスを他のプリンシパルと共有してはなりません。そのキーには認可結果を含めるか、パブリックな 402 チャレンジのみをキャッシュする必要があります。
参考文献
- RFC 9110 HTTP Semantics:402 の登録セマンティクスと HTTP ステータスの制約。
- x402 Introduction:チャレンジ駆動型でアカウントレスなリクエスト課金プロトコルの概念。
- Coinbase HTTP 402 Core Concepts:支払い要件、検証、およびリソースアクセスの実装境界。
フォローアップの質問
1つの支払い証明が2つのリソースに使用されるのを防ぐにはどうすればよいですか?
正規化されたメソッド、パス、クエリダイジェスト、またはリソース ID を支払い要件に含め、それらのフィールドを証明の対象とします。サービスは同じ正規化アルゴリズムでダイジェストを再計算し、各ノンスまたは支払い ID を1回限り有効として記録します。
402 チャレンジはヘッダーとレスポンスボディのどちらに含めるべきですか?
まずバージョン管理された機械可読なフォーマットを定義してください。ヘッダーは小規模なヒントに適しており、ボディは複数フィールドからなる支払い要件を保持するのに適しています。どちらの場合も、サイズを制限し、コンテンツタイプを宣言し、キャッシュ可能なヘッダーに機密性の高いクレデンシャルを含めないようにしてください。
支払いは成功したものの業務処理の実行に失敗した場合はどうなりますか?
リクエスト ID で紐付けられた個別の状態として、支払い、認可、実行、返金を管理します。処理がリトライ不可能な場合は、返金または手動突合キューに入れます。リトライ可能な場合は、処理中ステータスを返し、その後の読み取りで同一の結果を返すようにします。
x402 にはブロックチェーンが必須ですか?
現在の x402 の資料ではオンチェーンやファシリテーターによる支払いを例として使用していますが、HTTP 402 自体は特定の決済ネットワークを規定していません。ステータスコードのセマンティクスと決済レールは切り離してください。レールを変更する場合は、証明、確定性(finality)、および返金のセマンティクスを再定義する必要があります。
システムが決して二重請求しないことをどのように検証しますか?
支払い ID、リクエスト ID、および業務処理に対して一意制約を追加し、すべての検証および実行結果を記録します。そして、クライアントのタイムアウト、サービスの再起動、重複した検証コールバック、遅延した突合を網羅する障害注入テストを実施します。