本文详细介绍了如何在 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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!