本文介绍了如何使用 Solana Pay 创建一个简单的支付应用程序,包括支付处理和交易验证。文章详细讲解了 Solana Pay 的原理及其安全可靠的支付流程,并提供了代码示例和步骤,适合开发者学习和实践。
Solana Pay 是一种快速、易于使用、安全的支付解决方案,构建在 Solana 区块链上。在本指南中,你将学习如何使用 Solana Pay 创建一个简单的支付应用程序、处理支付以及验证交易。
使用 Solana Pay 创建一个简单的模拟:
敬请期待最后的奖励部分!
Solana Pay 是一个强大且灵活的 JavaScript 库,可实现 Solana 区块链上的无缝商业交易。通过利用 token 转让 URL 方案,Solana Pay 确保钱包与服务之间的互操作性,使企业和开发人员能够轻松接受以 SOL 或任何 SPL token 进行支付,而无需中介。由于 Solana 提供近实时的结算和低交易费用,Solana Pay 是减少买卖双方间摩擦的绝佳工具。
上面的图示说明了 Solana Pay 交易的过程,参与者包括客户端(用户的钱包)和服务器(商家的网站或应用程序)。
让我们来试试吧!
在开始构建你的 Solana Pay 应用程序之前,需要设置环境并安装所需的依赖项。打开终端并运行以下命令:
## 为你的项目创建一个新目录
mkdir solana-pay-demo
cd solana-pay-demo
## 创建启用 .json 导入的 **tsconfig.json**:
tsc -init --resolveJsonModule true
## 初始化新的 Node.js 项目并安装依赖项
npm init -y
npm install --save @solana/web3.js@1 @solana/pay bignumber.js
### 或者如果你更喜欢 yarn
yarn init -y
yarn add @solana/web3.js@1 @solana/pay bignumber.js
这将为你的项目创建一个新目录,使用 package.json
和 tsconfig
初始化,并安装所需的依赖项。
在你的项目目录中创建一个名为 index.ts 的新文件。在终端中输入:
echo > index.ts
当你准备好时,你的环境应该看起来像这样:
为了在 Solana 上构建,你需要一个 API 端点来连接网络。你可以使用公共节点,也可以部署和管理自己的基础设施;然而,如果你希望享受 8 倍的响应速度,可以把繁重的工作留给我们。
了解为什么超过 50% 的 Solana 项目选择 QuickNode,并在 此处 注册一个免费账号。我们将使用 Solana Devnet 端点。
复制 HTTP 提供者链接:
在你的 index.ts 文件顶部,导入所需的依赖项:
import { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, sendAndConfirmTransaction, SystemProgram, Transaction, TransactionInstruction } from '@solana/web3.js';
import { encodeURL, validateTransfer, parseURL, TransferRequestURL, findReference } from '@solana/pay';
import BigNumber from 'bignumber.js';
我们需要完成的第一项任务是创建一个带有钱包的帐户并为其提供资金。我们将使用下面的便捷工具自动生成一个新钱包并为空投 1 SOL。(如果你喜欢更手动的方式,你还可以使用 Keypair.generate()
和 requestAirdrop()
函数来完成此操作)。
🔑生成带有 Devnet SOL 的新钱包
创建新钱包
一旦成功生成密钥对,你将注意到两个新的常量:secret
和 payer
,一个 Keypair。secret
是一个 32 字节数组,用于生成公钥和私钥。payer
是一个 Keypair 实例,用于签署交易(我们已经空投了一些 Devnet SOL 来覆盖手续费)。如果尚未添加,请确保将其添加到你的代码中,如下所示:
const secret = [0,...,0]; // 替换为你的密钥
const payer = Keypair.fromSecretKey(new Uint8Array(secret));
你还需要定义一些我们将在应用程序中使用的常量。将以下代码添加到 payer
常量下方:
// 常量
const myWallet = 'DemoKMZWkk483hX4mUrcJoo3zVvsKhm8XXs28TuwZw9H'; // 替换为你的钱包地址(这是支付将发送到的目标)
const recipient = new PublicKey(myWallet);
const quickNodeEndpoint = 'https://example.solana-devnet.quiknode.pro/123456/'; // 替换为你的 QuickNode 端点
const connection = new Connection(quickNodeEndpoint, 'confirmed');
const amount = new BigNumber(0.1); // 0.1 SOL
const reference = new Keypair().publicKey;
const label = 'QuickNode Guide Store';
const message = `QuickNode Demo - Order ID #0${Math.floor(Math.random() * 999999) + 1}`;
const memo = 'QN Solana Pay Demo Public Memo';
将 myWallet
和 quickNodeEndpoint
替换为你的钱包地址和上一步骤中的 QuickNode 端点。
以下是每个常量的功能总结:
myWallet
是支付的接收地址。recipient
是支付的接收公钥,支付将发送到该地址。quickNodeEndpoint
是你在上一步创建的 QuickNode 端点。connection
是与 Solana 网络的连接。amount
是将要发送的 SOL 数量(以 SOL 为单位,而不是 lamports)。reference
(可选)是用于在链上验证交易的随机密钥(可以作为客户端 ID 使用) - 关于引用密钥的额外信息可以在 此处 找到。label
(可选)描述了转账请求的来源(例如,商店的名称、品牌、应用程序) - 这可能会被钱包用来在批准交易之前向买家展示信息。message
(可选)是描述转账请求性质的消息(例如,物品名称、订单 ID 或感谢函) - 这可能会被钱包用来在批准交易之前向买家展示信息。memo
(可选)是将包含在链上交易中的公共备注。以下是 message
和 label
的示例,在买家的 Phantom 钱包的批准流程中显示:
现在你已经设置好了环境,可以构建你的应用程序。
在这一步中,你将创建一个函数 generateUrl
,该函数通过生成支付请求 URL 来模拟购物体验:
async function generateUrl(
recipient: PublicKey,
amount: BigNumber,
reference: PublicKey,
label: string,
message: string,
memo: string
) {
console.log('1. 创建支付请求链接');
const url: URL = encodeURL({ recipient, amount, reference, label, message, memo });
console.log('支付请求链接:', url);
return url;
}
通常,你应该在后端服务器上生成支付请求 URL,以确保数据的安全性和完整性。通过在服务器端处理敏感数据和交易详细信息,你可以降低向恶意用户或第三方泄露关键信息的风险。这种方法还可以让你更好地控制和管理交易数据,使实现额外的安全措施变得更加容易,例如验证交易或监测欺诈活动。
然而,出于本指南的目的,我们将通过在客户端上运行该函数来简化此过程。
此时,你应该能够生成支付请求 URL。要测试它,调用你的函数:
(async () => {
try {
const url = await generateUrl(recipient, amount, reference, label, message, memo);
console.log('成功');
} catch (err) {
console.error(err);
}
})();
在终端中输入:
ts-node index.ts
你应该在终端中看到一个 URL 对象。请注意其 href
属性,它是你将用于发送支付的实际 URL。它的样子应该类似于 solana:DemoKMZWkk483hX4mUrcJoo3zVvsKhm8XXs28TuwZw9H?amount=0.1&reference=BurHXKwdNbcnShxWXL1khovANkYY6FWS4yTdN6FWCDsX&label=QuickNode+Guide+Store&message=QuickNode+Demo+-+Order+ID+%230405909&memo=QN+Solana+Pay+Demo+Public+Memo8。
如果你希望清理终端输出,可以删除或注释掉 console.log('支付请求链接:', url);
这一行。
干得不错。你已经成功生成了支付请求 URL。如果你正在构建一个实际的应用程序,你现在可能会将此 URL 发送给买家(通常通过二维码),买家随后将使用它发送支付。我们将在未来的指南中介绍构建前端和二维码,但你可以将 URL 的 href 传入二维码生成器 (例如:qr-code-generator.com) 进行测试。
让我们继续处理支付请求 URL。
接下来,你将创建 processPayment
函数,该函数解析支付请求链接以提取支付详细信息,创建新的 Solana 交易,并将其发送到 Solana 区块链。通常这将由用户的钱包应用或使用钱包适配器的去中心化应用程序 (dApp) 处理。前端组件将解析支付请求链接并通过用户的钱包启动资金转移。让我们看看如何做。
在你的 index.ts 文件中,添加以下代码:
async function processPayment(url: URL, payer: Keypair) {
// 解析支付请求链接
console.log('2. 解析支付请求链接');
const { recipient, amount, reference, label, message, memo } = parseURL(url) as TransferRequestURL;
if (!recipient || !amount || !reference) throw new Error('无效的支付请求链接');
console.log('3. 组装交易');
const tx = new Transaction();
// 如果提供了备注指令,则添加备注指令
if (memo != null) {
tx.add(
new TransactionInstruction({
programId: new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr'),
keys: [],
data: Buffer.from(memo, 'utf8'),
})
);
}
// 创建转移指令
const ix = SystemProgram.transfer({
fromPubkey: payer.publicKey,
toPubkey: recipient,
lamports: amount.multipliedBy(LAMPORTS_PER_SOL).integerValue(BigNumber.ROUND_FLOOR).toNumber()
});
// 如果提供了引用,则将引用公钥添加到指令
if (reference) {
const ref = Array.isArray(reference) ? reference : [reference];
for (const pubkey of ref) {
ix.keys.push({ pubkey, isWritable: false, isSigner: false });
}
}
// 将转移指令添加到交易中
tx.add(ix);
// 将交易发送到 Solana 网络并确认其被处理
console.log('4. 🚀 发送并确认交易');
const txId = await sendAndConfirmTransaction(connection, tx, [payer]);
console.log(` Tx: https://explorer.solana.com/tx/${txId}?cluster=devnet`);
}
让我们分解一下这里发生的事情:
@solana/pay
库中的 parseURL
函数解析支付请求链接。该函数返回一个包含支付详细信息的对象,包括接收者、金额、引用、标签、消息和备注,我们将其解构以便于访问。memo
编码并存储在链上交易数据中。SystemProgram.transfer
函数创建并添加转移指令。如果提供了引用密钥,我们也将其添加到指令中。到目前为止,我们已经构建了一个模拟环境,卖方可以生成支付请求链接,买家可以响应该请求并向供应商付款。最后,我们需要一种方式来验证供应商是否已收到了付款,且付款详细信息是否正确。
要验证支付是否已收到,你将创建 verifyPayment
函数,该函数将查询 Solana 区块链以检查付款是否已收到,并确保付款详细信息与原始请求匹配。在典型的环境中,这种类型的函数将由卖方的后端调用。对于这个简化的示例,我们也将从客户端处理它。在你的 index.ts 文件中,添加以下代码:
async function verifyTx(
recipient: PublicKey,
amount: BigNumber,
reference: PublicKey,
memo: string
) {
console.log(`5. 验证支付`);
// 商家应用通过在转账链接中提供的唯一引用地址查找交易签名
const found = await findReference(connection, reference);
// 商家应用应始终验证交易是否将预期金额转账给接收者
const response = await validateTransfer(
connection,
found.signature,
{
recipient,
amount,
splToken: undefined,
reference,
memo
},
{ commitment: 'confirmed' }
);
return response;
}
让我们分解一下这里发生的事情:
@solana/pay
库中的 findReference
函数通过集群查找交易签名。该方法对提供的引用地址执行 getSignaturesForAddress
RPC 调用。@solana/pay
库中的 validateTransfer
函数验证交易是否已将预期金额转账给接收者。该方法对提供的交易签名进行 getTransaction
RPC 调用。然后检查 recipient
是否收到了正确的 amount
,并且 memo
是否与预期值匹配。最后,让我们运行这个应用。在你的 index.ts 文件中,将之前的 async 块替换为以下内容:
(async () => {
try {
console.log(`⚡️ Solana Pay 演示 ⚡️`);
console.log(` 正在处理支付:`);
console.log(` - 金额 ${amount} SOL`);
console.log(` - 来自 ${payer.publicKey.toBase58()}`);
console.log(` - 发送到 ${recipient.toBase58()}`);
const url = await generateUrl(recipient, amount, reference, label, message, memo);
await processPayment(url, payer);
const response = await verifyTx(recipient, amount, reference, memo);
if (!response || response.meta?.err) throw new Error('未验证');
console.log('🎉 付款确认!');
} catch (err) {
console.error(err);
}
})();
现在,运行这个应用。在你的终端中输入:
ts-node index
你应该在终端中看到以下输出:
你应该能够通过终端中的 Explorer URL 查看 Solana 区块链上的交易(并看到备注和转账指令):
干得好!
额外奖励:SPL token
本练习对于创建和发送 SOL 付款非常有帮助,但如果你想发送 SPL token(例如 USDC)呢?Solana Pay 同样使发送 SPL token 变得简单。作为额外任务,请尝试修改 generateUrl
、processPayment
和 verifyTx
函数,以发送 SPL token 而不是 SOL。
提示:
generateUrl
函数添加一个 splToken
参数。createTransferCheckedInstruction
替换 SystemProgram.transfer
指令(你需要为 payer
和 recipient
使用 getOrCreateAssociatedTokenAccount
)。validateTransfer
函数添加一个 splToken
参数。在本指南中,你学习了如何使用 Solana Pay 创建一个简单的支付应用程序、处理支付并验证交易的基础知识。通过利用 Solana Pay,你可以轻松构建快速、安全和用户友好的支付解决方案,基于 Solana 区块链。
请记住,本指南提供的示例是简化的,在实际应用中,你需要考虑额外的安全措施、用户体验和错误处理。将敏感数据和交易处理在客户端和服务器端之间分开至关重要,以最大限度地降低暴露关键信息的风险。
如果你需要帮助或想与我们分享你正在构建的 Solana Pay 项目,请在 Discord 或 Twitter 上告诉我们。
如果你对本指南有任何反馈或问题,请告知我们。我们很乐意听到你的声音!
- 原文链接: quicknode.com/guides/sol...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!