Book

 · 大約1個月 ago

x402 payment js - Nov 4

import { Controller } from "@hotwired/stimulus"

export default class extends Controller { static values = { url: String }

async pay() { if (!window.ethereum) { alert("⚠️ 请先安装钱包") return }

try {
  // 1️⃣ 请求资源
  const res = await fetch(this.urlValue)
  const payInfo = await res.json()
  
  if (res.status !== 402) {
    alert("✅ 已解锁或无需支付")
    return
  }

  // 2️⃣ 保存支付要求
  const paymentRequirements = payInfo
  const accept = payInfo.accepts[0]
  const { payTo, asset, maxAmountRequired, network } = accept
  
  console.log("🧾 支付要求:", paymentRequirements)
  console.log("🧾 scheme 来自后端:", accept.scheme)  // 应该是 "exact"

  // 3️⃣ 连接钱包
  const [from] = await window.ethereum.request({ 
    method: "eth_requestAccounts" 
  })

  // 4️⃣ 生成 nonce
  const nonce = `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`

  // 5️⃣ 构造并发送交易
  const amountInUnits = BigInt(maxAmountRequired)
  const selector = "0xa9059cbb"
  const toPadded = payTo.replace(/^0x/, "").padStart(64, "0")
  const amountHex = amountInUnits.toString(16).padStart(64, "0")
  const data = selector + toPadded + amountHex

  const txHash = await window.ethereum.request({
    method: "eth_sendTransaction",
    params: [{ from, to: asset, data }]
  })

  console.log("💸 交易哈希:", txHash)

  // 6️⃣ 时间戳
  const timestamp = Math.floor(Date.now() / 1000)
  const validAfter = timestamp - 300
  const validBefore = timestamp + 600

  // 7️⃣ 构造 Payment Payload(注意结构!)
  const paymentPayload = {
    x402Version: 1,
    scheme: accept.scheme || "exact",  // ✅ 从后端获取或使用 "exact"
    network: network,
    payload: {
      authorization: {
        from: from,
        to: payTo,
        value: maxAmountRequired,
        validAfter: validAfter,
        validBefore: validBefore,
        nonce: nonce,
        asset: asset,
        txHash: txHash
      }
    }
  }

  // 8️⃣ 调试日志
  console.log("=" .repeat(50))
  console.log("🔍 支付数据结构检查")
  console.log("=" .repeat(50))
  console.log("paymentPayload:", JSON.stringify(paymentPayload, null, 2))
  console.log("✅ scheme 值:", paymentPayload.scheme)
  console.log("✅ network 值:", paymentPayload.network)
  
  if (!paymentPayload.scheme) {
    throw new Error("scheme 字段缺失!")
  }

  // 9️⃣ 构造验证请求
  const verifyRequest = {
    x402Version: 1,
    paymentPayload: paymentPayload,
    paymentRequirements: paymentRequirements
  }

  console.log("verifyRequest.paymentPayload.scheme:", verifyRequest.paymentPayload.scheme)

  // 🔟 编码
  const verifyRequestString = JSON.stringify(verifyRequest)
  console.log("编码前 JSON:", verifyRequestString.substring(0, 200) + "...")
  
  const encoded = btoa(verifyRequestString)

  // 验证解码
  const decoded = JSON.parse(atob(encoded))
  console.log("解码后 scheme:", decoded.paymentPayload.scheme)
  console.log("=" .repeat(50))

  if (!decoded.paymentPayload.scheme) {
    throw new Error("编码后 scheme 丢失!")
  }

  // 1️⃣1️⃣ 发送验证
  const verifyRes = await fetch(this.urlValue, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-PAYMENT": encoded
    },
    body: JSON.stringify({})
  })

  console.log("📬 响应状态:", verifyRes.status)

  if (verifyRes.ok) {
    const data = await verifyRes.json()
    alert("✅ 支付成功!内容已解锁")
    console.log("📄 内容:", data)
    window.location.reload()
  } else {
    const err = await verifyRes.text()
    console.error("❌ 验证失败:", err)
    alert(`❌ 验证失败: ${err}`)
  }

} catch (e) {
  console.error("💥 出错:", e)
  alert("💥 支付失败: " + e.message)
}


} }