如何在网页应用中用 Solana Pay 构建支付门户

QuickNode 发布于 2026-07-10 阅读 13

本文详细介绍了如何在Next.js 13和React应用中集成Solana Pay支付功能,通过创建API后端生成支付请求URL,生成QR码供用户扫描支付,并验证交易是否成功。文章包含完整的代码示例和测试步骤。

概述

你是否拥有一个在线商店,并希望添加由 Solana 提供支持的支付功能?Solana Pay 是一个基于 Solana 区块链的快速、易用且安全的支付解决方案。在本分步指南中,我们将向你展示如何利用 Solana Pay 通过二维码来接受支付。你将能够为客户的订单生成自定义二维码,客户扫描后即可完成结账!

你将完成什么

使用 Next.js 13 和 React 创建一个带有二维码的 Solana Pay 支付门户:

你的浏览器不支持视频标签。

我们将按以下步骤操作:

  1. 创建一个新的 Next.js 项目。
  2. 使用 React 构建一个简单的 UI。
  3. 使用 Next API 路由来创建一个后端终端,用于生成支付请求并验证支付成功。
  4. 为支付请求渲染二维码。
  5. 使用你手机上的 Phantom 钱包进行测试!

你需要准备什么

创建一个新的 Next.js 项目

首先,打开终端并运行以下命令来创建一个新的 Next.js 项目:

npx create-next-app@latest solana-pay-store
### 或
yarn create next-app solana-pay-store

系统会询问大约 5 个关于项目配置的问题。对于本指南,你可以接受所有默认值。这将在你的项目目录中创建一个新的 solana-pay-store 文件夹,并使用最新版本的 Next.js 进行初始化。进入你的新项目目录:

cd solana-pay-store

运行 yarn dev 启动开发服务器,并确保安装成功。这将在默认浏览器(通常是 localhost:3000)中打开项目。你应该会看到默认的 Next.js 欢迎页面:

Next.js 欢迎页面

做得好。关闭浏览器窗口,并在终端中按 Ctrl + C(Mac 上按 Cmd + C)停止开发服务器。

现在我们需要安装 Solana-web3.js 和 Solana Pay 包。运行以下命令:

npm install @solana/web3.js@1 @solana/pay
### 或
yarn add @solana/web3.js@1 @solana/pay

最后,你需要一个 Solana 端点来连接到 Solana 网络,以便在链上验证支付。

使用你的 QuickNode 端点连接到 Solana 集群

要在 Solana 上进行开发,你需要一个 API 端点来连接网络。你可以使用公共节点,也可以部署和管理自己的基础设施;但如果你希望获得 8 倍的响应速度,你可以把繁重的工作交给我们。

了解为什么 Solana 上超过 50% 的项目选择 QuickNode,并开始你的免费试用 这里。我们将使用一个 Solana Mainnet 端点。

复制 HTTP Provider 链接:

做得好。你已经准备好开始构建你的应用程序了。如果在设置过程中遇到问题或在本指南中遇到其他问题,请通过 Discord 联系我们。

创建后端

在本演示中,我们将使用 Next.js API 路由。API 路由是创建应用程序后端的好方法,无需设置单独的服务器。pages/api 文件夹中的任何文件都会被映射到 /api/*,并被视为 API 端点而不是页面。你可以在此处阅读更多关于 Next.js API 路由的信息 这里

导航到 pages/api 并删除 hello.ts。我们将用我们自己的 API 路由替换这个文件。创建一个名为 pay.ts 的新文件,并添加以下代码:

import { NextApiRequest, NextApiResponse } from 'next';
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import { encodeURL, findReference, validateTransfer } from '@solana/pay';
import BigNumber from 'bignumber.js';

// 常量
const myWallet = 'DemoKMZWkk483hX4mUrcJoo3zVvsKhm8XXs28TuwZw9H'; // 替换为你的钱包地址(这是支付的目标地址)
const recipient = new PublicKey(myWallet);
const amount = new BigNumber(0.0001); // 0.0001 SOL
const label = 'Quicknode 指南商店';
const memo = 'QN Solana Pay 演示公共备注';
const quicknodeEndpoint = 'https://example.solana-devnet.quiknode.pro/123456/'; // 替换为你的 QuickNode 端点

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  // 处理生成支付请求
  if (req.method === 'POST') {

  // 处理验证支付请求
  } else if (req.method === 'GET') {

  // 处理无效请求
  } else {
    res.status(405).json({ error: '方法不允许' });
  }
}

我们在这里定义了一些在整个应用程序中使用的关键变量。我们还从 Solana Pay 和 Solana-web3.js 导入了必要的包。请注意,我们在演示中定义了一些常量——你可能需要根据应用程序的需求使其中一些值可变(例如 amountmemorecipient)。确保将 quicknodeEndpoint 更新为你的 QuickNode 端点,并将 myWallet 更新为你的公钥(这是支付的目标地址——如果错误,支付 URL 会将支付导向错误的地址)。

我们还定义了 API 处理器。我们将使用一个处理器来处理生成支付请求和验证支付请求。我们使用 req.method 属性来决定执行哪个操作。如果请求方法是 POST,我们将生成一个支付请求。如果请求方法是 GET,我们将验证一个支付请求。如果请求方法是其他任何内容,我们将返回一个错误。你也可以为每个操作使用单独的处理器,但为了简单起见,我们将使用一个处理器。

生成支付请求

我们将使用我们在 Solana Pay 入门指南 中创建的一些工具来开始。在 pay.ts 的顶层,handler 函数之前添加一个 generateUrl 函数。这将生成一个 Solana 支付请求 URL,我们可以用它来生成二维码,供客户扫描并使用 Solana Pay 支付。

async function generateUrl(
  recipient: PublicKey,
  amount: BigNumber,
  reference: PublicKey,
  label: string,
  message: string,
  memo: string,
) {
  const url: URL = encodeURL({
    recipient,
    amount,
    reference,
    label,
    message,
    memo,
  });
  return { url };
}

接下来,我们需要一种存储支付请求信息的方法。在本演示中,我们将使用一个简单的内存数据结构来存储支付请求信息。这将允许我们稍后验证支付请求。在 pay.ts 中添加一个 paymentRequests Map

const paymentRequests = new Map<string, { recipient: PublicKey; amount: BigNumber; memo: string }>();

这将允许我们使用 reference 作为键来存储支付请求信息。我们将使用 reference 作为键,因为它是每个支付请求的唯一标识符。我们还将存储 recipientamountmemo,以便稍后验证支付请求。请记住,这种方法仅适用于小型应用程序或开发阶段。在生产环境或更大型的应用程序中,你应该考虑使用更持久的存储解决方案(如数据库,例如 PostgreSQL、MongoDB、Redis)来存储支付请求信息。这将确保数据在服务器重启时不会丢失,并且如果你有分布式或负载均衡系统,可以在多个服务器实例之间访问。

现在让我们更新处理器,使用 generateUrl 函数并将支付请求信息存储到 paymentRequests Map 中。在 pay.tsPOST 处理器中添加以下代码:

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === 'POST') {
    try {
      const reference = new Keypair().publicKey;
      const message = `Quicknode 演示 - 订单号 #0${Math.floor(Math.random() * 999999) + 1}`;
      const urlData = await generateUrl(
        recipient,
        amount,
        reference,
        label,
        message,
        memo
      );
      const ref = reference.toBase58();
      paymentRequests.set(ref, { recipient, amount, memo });
      const { url } = urlData;
      res.status(200).json({ url: url.toString(), ref });
    } catch (error) {
      console.error('错误:', error);
      res.status(500).json({ error: '内部服务器错误' });
    }
  }
  //...
}

以下是我们所做的分解:

  1. 我们生成一个新的 reference 密钥对,并将公钥存储在 reference 变量中。这是随机生成的,每个支付请求将是唯一的。
  2. 我们生成一条 message 来显示用户的订单 ID。出于演示目的,我们生成一个随机的订单 ID。你可以根据自己的需要传递任何消息给用户(当他们被提示支付时,应该会在他们的钱包中显示)。
  3. 我们调用 generateUrl 函数生成支付请求 URL。我们传入之前定义的 recipientamountreferencelabelmessagememo 变量。这将生成一个支付请求 URL,我们将其存储为 urlData
  4. 我们将 reference 公钥转换为 base58 字符串并存储为 ref,我们将其作为键,使用 .set 将支付请求信息存储到 paymentRequests Map 中。
  5. 我们向客户端响应一个 200 状态码(成功)、支付请求 urlref 键。

让我们测试一下!运行以下命令启动服务器:

npm run dev
## 或
yarn dev

然后在另一个终端窗口中,运行以下 cURL 脚本向 /api/pay 端点发送 POST 请求:

curl -X POST http://localhost:3000/api/pay

你应该会看到类似以下的响应:

示例 POST 响应

干得好!你刚刚创建了一个 API 端点,它生成支付请求 URL 并将支付请求信息存储在内存中。现在让我们创建一个端点来验证支付请求。

验证支付请求

在进入前端之前,我们需要后端进行一些验证,以确保支付请求有效并且支付已由我们的 recipient 钱包接收。我们将使用之前生成的 reference 密钥对来查找支付。我们将使用 recipientamountmemo 字段来验证支付是否发送到了正确的接收方,并且支付金额是否正确(请记住,我们将这些值存储在 paymentRequests Map 中,以便后端知道要查找什么)。

pay.ts 中创建一个 verifyTransaction 函数:

async function verifyTransaction(reference: PublicKey) {
  // 1 - 检查支付请求是否存在
  const paymentData = paymentRequests.get(reference.toBase58());
  if (!paymentData) {
    throw new Error('支付请求未找到');
  }
  const { recipient, amount, memo } = paymentData;
  // 2 - 建立与 Solana 集群的连接
  const connection = new Connection(quicknodeEndpoint, 'confirmed');
  console.log('接收方', recipient.toBase58());
  console.log('金额', amount);
  console.log('引用', reference.toBase58());
  console.log('备注', memo);

  // 3 - 查找交易引用
  const found = await findReference(connection, reference);
  console.log(found.signature)

  // 4 - 验证交易
  const response = await validateTransfer(
    connection,
    found.signature,
    {
      recipient,
      amount,
      splToken: undefined,
      reference,
      //memo
    },
    { commitment: 'confirmed' }
  );
  // 5 - 从本地存储中删除支付请求并返回响应
  if (response) {
    paymentRequests.delete(reference.toBase58());
  }
  return response;
}

以下是我们所做的分解:

  1. 我们检查支付请求是否存在于 paymentRequests Map 中。如果不存在,则抛出错误。我们只应验证我们生成的支付请求。
  2. 我们建立与 Solana 集群的连接。我们使用之前定义的 quicknodeEndpoint 变量。重要:支付者必须连接到与后端指定的相同的 Solana 集群。如果买家使用了错误的集群,将无法找到支付。
  3. 我们查找交易引用。这是包含支付请求信息的交易。我们使用从 solana-pay 导入的 findReference 函数,并传入 reference 参数。
  4. 我们验证交易。我们使用从 solana-pay 导入的 validateTransfer 函数。如果找到有效的支付,它将返回一个 TransactionResponse;如果未找到或支付无效,则返回错误。
  5. 如果支付有效,我们从 paymentRequests Map 中删除该支付请求,并返回 TransactionResponse

现在我们已经有了 verifyTransaction 函数,让我们更新 handler。在 handler 函数的 GET 条件中,添加以下代码:

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  // ...
  else if (req.method === 'GET') {
    // 1 - 从 NextApiRequest 中获取 reference 查询参数
    const reference = req.query.reference;
    if (!reference) {
      res.status(400).json({ error: '缺少 reference 查询参数' });
      return;
    }
    // 2 - 验证交易
    try {
      const referencePublicKey = new PublicKey(reference as string);
      const response = await verifyTransaction(referencePublicKey);
      if (response) {
        res.status(200).json({ status: 'verified' });
      } else {
        res.status(404).json({ status: 'not found' });
      }
    } catch (error) {
      console.error('错误:', error);
      res.status(500).json({ error: '内部服务器错误' });
    }
  }
  // ...
}

以下是代码的快速分解:

  1. 我们从 NextApiRequest 中获取 reference 查询参数并存储为 reference。如果没有提供引用,则返回 400 状态码(错误请求)。请注意,我们期望前端将 reference 查询参数作为字符串传入。我们将在下一步中将其转换为 PublicKey
  2. 我们验证交易。我们将 reference 查询参数作为 PublicKey 传入,并将响应存储为 response。如果响应有效,我们返回 200 状态码(成功)和 verified 状态。如果响应无效,我们返回 404 状态码(未找到)和 not found 状态。如果出现错误,我们返回 500 状态码(内部服务器错误)和错误消息。

创建前端

后端已经设置好,现在让我们创建一个前端来与我们的 API 交互。首先,将默认的 pages/index.tsx 文件内容替换为以下代码:

import Head from 'next/head';
import Image from 'next/image';
import { useState } from 'react';
import { createQR } from '@solana/pay';

export default function Home() {

  const handleGenerateClick = async () => {

  };

  const handleVerifyClick = async () => {

  };

  return (
    <>
      <Head>
        <title>Quicknode Solana Pay 演示</title>
        <meta name="description" content="Quicknode 指南:Solana Pay" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className="flex min-h-screen flex-col items-center justify-between p-24">
        <div className="z-10 w-full max-w-5xl items-center justify-between font-mono text-sm lg:flex">
          <h1 className='text-2xl font-semibold'>Solana Pay 演示</h1>
        </div>
        {<Image
          style={{ position: "relative", background: "white" }}
          src={''}
          alt="二维码"
          width={200}
          height={200}
          priority
        />}
        <div>
          <button
            style={{ cursor: 'pointer', padding: '10px', marginRight: '10px' }}
            onClick={handleGenerateClick}
          >
            生成 Solana Pay 订单
          </button>
          {<button
            style={{ cursor: 'pointer', padding: '10px' }}
            onClick={handleVerifyClick}
          >
            验证交易
          </button>}
        </div>
      </main>
    </>
  );
}

我们将应用程序的主页改为一个简单的 UI,允许我们生成 Solana Pay 订单并验证交易。我们导入了必要的依赖项,并创建了两个空函数:handleGenerateClickhandleVerifyClick。每个函数在页面上都有一个对应的按钮。我们将在下一步中填充这些函数的功能。如果你现在运行应用,你应该会看到一个带有两个按钮的简单 UI:

Solana Pay 演示

实现二维码生成器

在同一个 pages/index.tsx 文件中,让我们首先创建两个状态变量:qrCodereference。我们将使用这些变量来存储二维码和交易引用。在 Home 函数的顶部添加以下代码:

  const [qrCode, setQrCode] = useState<string>();
  const [reference, setReference] = useState<string>();

我们将生成一个二维码作为 base64 字符串,并将其存储在 qrCode 状态变量中,以便我们可以将其传递给 Image 组件。我们还将交易引用存储在 reference 状态变量中,以便稍后用于验证交易。

现在,让我们实现 handleGenerateClick 函数。我们将使用此函数向后端发送 POST 请求,并使用 URL 响应创建二维码。在 handleGenerateClick 函数中添加以下代码:

  const handleGenerateClick = async () => {
    // 1 - 向后端发送 POST 请求并记录响应 URL
    const res = await fetch('/api/pay', { method: 'POST' });
    const { url, ref } = await res.json();
    console.log(url)
    // 2 - 从 URL 生成二维码并生成一个 blob
    const qr = createQR(url);
    const qrBlob = await qr.getRawData('png');
    if (!qrBlob) return;
    // 3 - 将 blob 转换为 base64 字符串(使用 FileReader)并设置二维码状态
    const reader = new FileReader();
    reader.onload = (event) => {
      if (typeof event.target?.result === 'string') {
        setQrCode(event.target.result);
      }
    };
    reader.readAsDataURL(qrBlob);
    // 4 - 设置引用状态
    setReference(ref);
  };

让我们分解 handleGenerateClick 函数:

  1. 我们向后端(/api/pay)发送 POST 请求,然后解构并存储响应的 urlref 属性。
  2. 我们使用 @solana/pay 中的 createQR 函数从 url 生成一个二维码。然后我们使用 png 格式从二维码创建一个 blob,并将其存储在 qrBlob 中。
  3. 我们使用 FileReader 将 blob 转换为 base64 字符串,并将其存储在 qrCode 中。然后我们将 qrCode 状态设置为该 base64 字符串。
  4. 我们将 reference 的状态设置为响应中的 ref 属性。这将允许我们稍后验证交易。

现在我们已经生成了二维码,让我们更新页面渲染,以便在可用时显示它。将 return 语句中的 Image 组件更新为以下内容:

        {qrCode && (
          <Image
            src={qrCode}
            style={{ position: "relative", background: "white" }}
            alt="二维码"
            width={200}
            height={200}
            priority
          />
        )}
  • qrCode && 会在 qrCode 未设置时隐藏我们的 Image 组件,当设置时则显示二维码(src={qrCode})。

如果你现在运行应用,当你点击“生成 Solana Pay 订单”按钮时,你应该会看到生成的二维码!让我们创建一个函数来验证交易,以完成支付流程。

实现交易验证

现在我们已经生成了二维码,让我们创建一个函数来验证交易,以完成支付流程。我们将使用之前保存的 reference 状态传递给后端并验证交易。在 handleVerifyClick 函数中添加以下代码:

  const handleVerifyClick = async () => {
    // 1 - 检查引用是否已设置
    if (!reference) {
      alert('请先生成支付订单');
      return;
    }
    // 2 - 向后端发送 GET 请求并返回响应状态
    const res = await fetch(`/api/pay?reference=${reference}`);
    const { status } = await res.json();

    // 3 - 告知用户交易是否已验证,并重置二维码和引用
    if (status === 'verified') {
      alert('交易已验证');
      setQrCode(undefined);
      setReference(undefined);
    } else {
      alert('未找到交易');
    }
  };

以下是 handleVerifyClick 函数中的操作:

  1. 我们检查 reference 状态是否已设置。如果未设置,我们提示用户先生成支付订单。
  2. 我们向后端(/api/pay)发送 GET 请求(默认通过 fetch 方法),并将 reference 状态作为查询参数传递。然后我们解构并存储响应的 status 属性。
  3. 如果交易已验证,我们告知用户并重置 qrCodereference 状态。

最后,如果 reference 状态未设置,让我们隐藏“验证交易”按钮。将 return 语句中的 Button 组件更新为以下内容:

          {reference && <button
            style={{ cursor: 'pointer', padding: '10px' }}
            onClick={handleVerifyClick}
          >
            验证交易
          </button>}

干得好!现在来测试支付流程。

测试支付流程

通过在终端中运行以下命令启动你的支付终端:

npm run dev
### 或
yarn dev

按照以下步骤测试你的支付流程:

  1. 导航到 localhost:3000
  2. 在浏览器中,点击“生成 Solana Pay 订单”按钮。你应该会看到生成的二维码。
  3. 使用支持 Solana Pay 的钱包(例如 Phantom)扫描二维码。

重要 - 验证你的集群

确保你的钱包连接到了正确的集群(例如 Devnet)。如果你连接到的集群与后端指定的不同,你将无法验证交易。你可以通过进入“开发者设置”并选择正确的集群来更改钱包的集群。

如果你的钱包设置为主网(mainnet),则会按照后端指定的金额转移真实资金

如果你需要 devnet SOL,可以在这里请求:

🪂 请求 Devnet SOL

注意: 过于频繁地发送空投请求可能会触发 429(请求过多)错误。

空投 1 SOL(Devnet)

  1. 在钱包中验证交易详情是否符合预期,然后在钱包中批准支付。等待钱包中出现成功消息。
  2. 完成支付后,点击“验证交易”按钮。你应该会看到一条交易已验证的提示!请注意,如果你在网络确认交易之前点击此按钮,你将看到交易未找到的提示。

以下是验证支付后最终支付终端的外观:

Solana Pay 终端

做得好!

如果你想查看我们的完整代码,请查看我们的 GitHub 页面 这里

总结

恭喜!你已经成功使用 Next.js 13 和 @solana/pay 库构建了一个 Solana Pay 终端!使用此终端,你可以通过二维码接受 Solana 支付,这些二维码可以被兼容 Solana Pay 的钱包应用扫描。要在生产环境中部署此项目,你应该考虑使用更持久的存储解决方案(如数据库,例如 PostgreSQL、MongoDB、Redis)来存储支付请求信息。这将确保数据在服务器重启时不会丢失,并且如果你有分布式或负载均衡系统,可以在多个服务器实例之间访问。

如果你需要帮助或想分享你正在使用 Solana Pay 构建的内容,请通过 DiscordTwitter 告诉我们。

我们 ❤️ 反馈!

如果你对本指南有任何反馈或问题,请告诉我们。我们很乐意听取你的意见!

资源

  • 原文链接: quicknode.com/guides/sol...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~

相关文章

0 条评论