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

  • QuickNode
  • 发布于 2025-01-30 12:53
  • 阅读 17

本文详细介绍了如何在 Next.js 13 和 React 中集成 Solana Pay,以创建一个支持 QR 码支付的在线商店。文章提供了逐步的指南,涵盖从项目设置到后端 API 的创建和前端功能实现,强调了生成支付请求、验证交易的流程,以及如何使用 Solana 区块链进行支付。

概述

有一个在线商店想要通过 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 倍的快速响应时间,可以把繁重的负担留给我们。

了解为什么超过 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 中导入所需的包。请注意,我们在此处为演示定义了一些常量—你可能希望根据应用需求使这些值可变(例如,amountmemorecipient)。确保使用你的 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 作为键,因为它是每个支付请求的唯一标识符。我们还将存储 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 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' });
    }
  }
  // ...
}

这里是我们所做的事情的分解:

  1. 我们生成一个新的 reference 密钥对并将公钥存储在 reference 变量中。此密钥是随机生成的,并将对每个支付请求是唯一的。
  2. 我们生成一个 message 显示用户的订单 ID。出于演示目的,我们生成了一个随机订单 ID。随意向用户传递任何消息(这应该显示在他们的钱包中,当他们被提示支付时)。
  3. 我们调用 generateUrl 函数以生成支付请求 URL。我们传入之前定义的 recipientamountreferencelabelmessagememo 变量。这将生成一个我们存储为 urlData 的支付请求 URL。
  4. 我们将 reference 公钥转换为 base58 字符串,并将其存储为 ref,我们使用该值作为密钥在我们的 paymentRequests Map 中存储支付请求信息(使用 .set)。
  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('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;
}

这是我们所做的事情的分解:

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

这是我们代码的简要分解:

  1. 我们从 NextApiRequest 获取 reference 查询参数并将其存储为 reference。如果未提供任何引用,我们返回 400 状态码(错误请求)。请注意,我们期望前端以字符串的形式传入 reference 查询参数。我们将在下一步中将其转换为 PublicKey
  2. 我们验证交易。我们将 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 订单并验证交易。我们导入了必要的依赖项并创建了两个空函数:handleGenerateClickhandleVerifyClick。每个函数在页面上都有一个对应的按钮。我们将在下一步中填充这些函数的功能。如果你现在运行应用,你应该会看到一个包含两个按钮的简单 UI:

Solana Pay 演示

实现 QR 生成器

在同样的 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 - 使用 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 函数:

  1. 我们向后端(/api/pay)发送一个 POST 请求,然后解构并存储响应的 urlref 属性。
  2. 我们使用来自 @solana/paycreateQR 函数从 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="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 函数中做的事情:

  1. 我们检查 reference 状态是否设置。如果未设置,我们提醒用户先生成支付订单。
  2. 我们向后端(/api/pay)发送一个 GET 请求,并将 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)。如果你的钱包与后端指定的集群不一致,将无法验证交易。你可以通过转到“开发者设置”并选择正确的集群,来更改钱包的集群。

如果你的钱包设置为主网,将使用后端所述的真实资金进行转账。

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

🪂请求 Devnet SOL

空投 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
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
QuickNode
QuickNode
江湖只有他的大名,没有他的介绍。