本文详细介绍了如何在 Next.js 13 和 React 中集成 Solana Pay,以创建一个支持 QR 码支付的在线商店。文章提供了逐步的指南,涵盖从项目设置到后端 API 的创建和前端功能实现,强调了生成支付请求、验证交易的流程,以及如何使用 Solana 区块链进行支付。
有一个在线商店想要通过 Solana 添加支付功能吗?Solana Pay 是一个快速、易于使用、安全的支付解决方案,建立在 Solana 区块链上。在本逐步指南中,我们将向你展示如何利用 Solana Pay 通过二维码接受支付。你将能够为客户的订单生成自定义二维码,客户将能够扫描它进行结账!
使用 Next.js 13 和 React 创建一个带有二维码的 Solana Pay 支付门户:
你的浏览器不支持视频标签。
我们将遵循的步骤:
要开始,请打开终端并运行以下命令以创建一个新的 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 登陆页面:

做得很好。可以关闭浏览器窗口,并在终端中按 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 网络以验证链上的支付。
要在 Solana 上构建,你需要一个 API 端点与网络连接。你可以使用公共节点或自行部署和管理自己的基础设施;但是,如果你希望获得 8 倍的快速响应时间,可以把繁重的负担留给我们。
了解为什么超过 50% 的 Solana 项目选择 QuickNode,并在 这里 注册免费账户。我们将使用 Solana 主网端点。
复制 HTTP 提供者链接:
做得很好。你准备好开始构建你的应用了。如果你在设置或本指南中遇到任何其他问题,请在 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 Guide Store';
const memo = 'QN Solana Pay Demo Public Memo';
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: 'Method Not Allowed' });
  }
}我们在这里定义了一些将在整个应用中使用的关键变量。我们还从 Solana Pay 和 Solana-web3.js 中导入所需的包。请注意,我们在此处为演示定义了一些常量—你可能希望根据应用需求使这些值可变(例如,amount、memo 或 recipient)。确保使用你的 QuickNode 端点更新 quicknodeEndpoint 和你的公钥更新 myWallet(这是支付将要发送的目的地—如果这个值不正确,你的支付 URL 将支付到错误的地址)。
我们还定义了 API 响应处理程序。我们将使用一个处理程序来处理生成的支付请求和验证支付请求。我们使用 req.method 属性来确定采取哪种操作。如果请求方法是 POST,我们将生成支付请求。如果请求方法是 GET,我们将验证支付请求。如果请求方法是其他任何内容,我们将返回错误。你也可以为每个操作使用单独的处理程序,但为简单起见,我们将使用一个处理程序。
我们将使用我们在 关于开始使用 Solana Pay 的指南 中创建的一些工具来开始。将 generateUrl 函数添加到 pay.ts 的顶层,在 handler 函数之前。这将生成一个 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 作为键,因为它是每个支付请求的唯一标识符。我们还将存储 recipient、amount 和 memo 以便稍后验证支付请求。请记住,这种方法仅适合小规模应用或在开发期间。在生产环境中或对于更大规模的应用,你应该考虑使用更持久的存储解决方案,如数据库(例如,PostgreSQL、MongoDB、Redis)来存储支付请求信息。这将确保在服务器重启时数据不会丢失,并可以通过多个服务器实例访问,如果你有分布式或负载均衡的系统。
现在让我们更新我们的处理程序,以便使用 generateUrl 函数并在我们的 paymentRequests Map 中存储支付请求信息。在 pay.ts 的 POST 处理程序内部添加以下代码:
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === 'POST') {
    try {
      const reference = new Keypair().publicKey;
      const message = `QuickNode Demo - Order ID #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:', error);
      res.status(500).json({ error: 'Internal Server Error' });
    }
  }
  // ...
}这里是我们所做的事情的分解:
reference 密钥对并将公钥存储在 reference 变量中。此密钥是随机生成的,并将对每个支付请求是唯一的。message 显示用户的订单 ID。出于演示目的,我们生成了一个随机订单 ID。随意向用户传递任何消息(这应该显示在他们的钱包中,当他们被提示支付时)。generateUrl 函数以生成支付请求 URL。我们传入之前定义的 recipient、amount、reference、label、message 和 memo 变量。这将生成一个我们存储为 urlData 的支付请求 URL。reference 公钥转换为 base58 字符串,并将其存储为 ref,我们使用该值作为密钥在我们的 paymentRequests Map 中存储支付请求信息(使用 .set)。200 状态码(成功)、支付请求 url 和 ref 密钥。让我们测试一下!运行以下命令以启动服务器:
npm run dev
## 或
yarn dev然后在一个单独的终端窗口中,运行以下 cURL 脚本,对 /api/pay 端点发送一个 POST 请求:
curl -X POST http://localhost:3000/api/pay你应该会看到类似以下内容的响应:

干得不错!你刚刚创建了一个 API 端点,该端点生成一个支付请求 URL,并将支付请求信息存储在内存中。现在,让我们创建一个端点来验证支付请求。
在我们继续前端之前,我们需要我们的后端做一些验证,以确保支付请求是有效的,并且支付已经被我们的 recipient 钱包接收。我们将使用之前生成的 reference 密钥对来找到该支付。我们将使用 recipient、amount 和 memo 字段来验证支付是否已支付到正确的收件人并且支付金额是正确的(请记住,我们在 paymentRequests Map 中存储了这些值,因此我们的后端知道要查找什么)。
在 pay.ts 中创建一个 verifyTransaction 函数:
async function verifyTransaction(reference: PublicKey) {
  // 1 - 检查支付请求是否存在
  const paymentData = paymentRequests.get(reference.toBase58());
  if (!paymentData) {
    throw new Error('Payment request not found');
  }
  const { recipient, amount, memo } = paymentData;
  // 2 - 建立与 Solana 集群的连接
  const connection = new Connection(quicknodeEndpoint, 'confirmed');
  console.log('recipient', recipient.toBase58());
  console.log('amount', amount);
  console.log('reference', reference.toBase58());
  console.log('memo', 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;
}这是我们所做的事情的分解:
paymentRequests Map 中。如果不存在,我们抛出一个错误。我们应该只验证我们生成的支付请求。quicknodeEndpoint 变量。重要:付款人必须连接到与后端中指定的相同 Solana 集群。如果买家使用错误的集群,则将找不到付款。solana-pay 导入的 findReference 函数并传入 reference 参数。solana-pay 导入的 validateTransfer 函数。这将返回一个 TransactionResponse,如果找到有效支付,则返回;如果未找到支付或支付无效,则返回错误。paymentRequests Map 中删除支付请求并返回 TransactionResponse。现在我们已经有了 verifyTransaction 函数,让我们来更新我们的处理程序。在 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: 'Missing reference query parameter' });
      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:', error);
      res.status(500).json({ error: 'Internal Server Error' });
    }
  }
  // ...
}这是我们代码的简要分解:
reference 查询参数并将其存储为 reference。如果未提供任何引用,我们返回 400 状态码(错误请求)。请注意,我们期望前端以字符串的形式传入 reference 查询参数。我们将在下一步中将其转换为 PublicKey。reference 查询参数作为 PublicKey 传入,并将响应存储为 response。如果响应有效,我们返回 200 状态码(成功)和 verified 状态。如果响应无效,我们返回 404 状态码(未找到)和未找到状态。如果发生错误,我们返回 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 Demo</title>
        <meta name="description" content="QuickNode Guide: 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 Demo</h1>
        </div>
        {<Image
          style={{ position: "relative", background: "white" }}
          src={''}
          alt="QR Code"
          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 订单并验证交易。我们导入了必要的依赖项并创建了两个空函数:handleGenerateClick 和 handleVerifyClick。每个函数在页面上都有一个对应的按钮。我们将在下一步中填充这些函数的功能。如果你现在运行应用,你应该会看到一个包含两个按钮的简单 UI:

在同样的 pages/index.tsx 文件中,让我们开始创建两个状态变量:qrCode 和 reference。我们将使用这些变量来存储二维码和交易引用。将以下代码添加到 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 - 使用 FileReader 将 blob 转换为 base64 字符串并设置 QR 代码状态
    const reader = new FileReader();
    reader.onload = (event) => {
      if (typeof event.target?.result === 'string') {
        setQrCode(event.target.result);
      }
    };
    reader.readAsDataURL(qrBlob);
    // 4 - 设置引用状态
    setReference(ref);
  };让我们分解一下 handleGenerateClick 函数:
/api/pay)发送一个 POST 请求,然后解构并存储响应的 url 和 ref 属性。@solana/pay 的 createQR 函数从 url 生成二维码。然后我们使用 png 格式从二维码创建一个 blob,并将其存储在 qrBlob 中。FileReader 将 blob 转换为 base64 字符串,并将其存储于 qrCode 中。然后设置 qrCode 状态为 base64 字符串。reference 状态设置为响应中的 ref 属性。这将使我们能够稍后验证交易。现在我们已经生成了二维码,让我们更新页面渲染,当二维码可用时显示它。将 return 语句中的 Image 组件更新为以下内容:
        {qrCode && (
          <Image
            src={qrCode}
            style={{ position: "relative", background: "white" }}
            alt="QR Code"
            width={200}
            height={200}
            priority
          />
        )}qrCode && 将在 qrCode 未设置时隐藏我们的 Image 组件,当 qrCode 被设置时将显示二维码(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 函数中做的事情:
reference 状态是否设置。如果未设置,我们提醒用户先生成支付订单。/api/pay)发送一个 GET 请求,并将 reference 状态作为查询参数传入。然后,我们解构和存储响应的 status 属性。qrCode 和 reference 状态。最后,让我们在 reference 状态未设置时隐藏“验证交易”按钮。将 return 语句中的 Button 组件更新为如下内容:
          {reference && <button
            style={{ cursor: 'pointer', padding: '10px' }}
            onClick={handleVerifyClick}
          >
            验证交易
          </button>}太好了!让我们测试一下支付流程。
通过在终端中运行以下命令启动你的支付终端:
npm run dev
### 或
yarn dev通过以下步骤测试你的支付流程:
重要 - 验证你的集群
确保钱包处于正确的集群(例如,Devnet)。如果你的钱包与后端指定的集群不一致,将无法验证交易。你可以通过转到“开发者设置”并选择正确的集群,来更改钱包的集群。
如果你的钱包设置为主网,将使用后端所述的真实资金进行转账。
如果你需要 Devnet SOL,可以在这里请求:
🪂请求 Devnet SOL
空投 1 SOL(Devnet)
在验证支付后,我们的最终支付终端如下所示:

出色的工作!
如果你想查看我们完整的代码,请查看我们的 GitHub 页面 这里。
恭喜你,成功使用 Next.js 13 和 @solana/pay 库构建了 Solana Pay 终端!使用此终端,你可以接受 Solana 的支付,自生成可供支持 Solana Pay 的钱包应用扫描的二维码。要在生产环境中部署该项目,你应该考虑使用更持久的存储解决方案,如数据库(例如,PostgreSQL、MongoDB、Redis)来存储支付请求信息。这将确保数据不会在服务器重启时丢失,并且如果你有分布或负载均衡的系统,可以通过多个服务器实例访问数据。
如果你需要帮助或想让我们知道你正在使用 Solana Pay 构建什么,请在 Discord 或 Twitter 上与我们联系。
如果你对此指南有任何反馈或问题, 请告知我们。我们期待你的反馈!
- 原文链接: quicknode.com/guides/sol...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
 
                如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!