Las solicitudes de transacción permiten flujos de pago interactivos al permitir que las billeteras se comuniquen con tu servidor para componer cualquier transacción de Solana. Esto desbloquea casos de uso avanzados como acuñación de NFT, precios dinámicos, transacciones DeFi de múltiples pasos y lógica empresarial personalizada.
Descripción General
Las solicitudes de transacción siguen este formato de URL:
solana:<server-endpoint-url>
La billetera realiza dos solicitudes HTTP a tu endpoint:
- Solicitud GET - Recuperar información de pago (etiqueta, icono)
- Solicitud POST - Obtener la transacción para firmar
Configuración Básica
1. Instalar Dependencias
pnpm add @solana/pay@beta @solana/kit @solana-program/system \@solana/kit-plugin-rpc @solana/kit-plugin-payer @solana/kit-plugin-instruction-plan
2. Crear Endpoint de API
Configura un endpoint de API que maneje tanto solicitudes GET como POST:
// pages/api/pay.ts (Next.js) or similar endpointimport { address, createNoopSigner, lamports } from "@solana/kit";import { getTransferSolInstruction } from "@solana-program/system";import { createMerchantClient } from "@solana/pay";const MERCHANT_WALLET = address(process.env.MERCHANT_WALLET);export default async function handler(req, res) {if (req.method === "GET") {return handleGet(req, res);}if (req.method === "POST") {return handlePost(req, res);}return res.status(405).json({ error: "Method not allowed" });}// GET request handlerasync function handleGet(req, res) {const label = "My Store";const icon = "https://mystore.com/icon.png";res.status(200).json({label,icon});}// POST request handlerasync function handlePost(req, res) {try {const { account } = req.body;if (!account) {return res.status(400).json({ error: "Missing account parameter" });}const buyerAddress = address(account);// Create the merchant client for building the transactionconst merchant = createMerchantClient({rpcUrl: process.env.SOLANA_RPC_URL});// Build transfer instruction (createNoopSigner because the wallet signs later)const instruction = getTransferSolInstruction({source: createNoopSigner(buyerAddress),destination: MERCHANT_WALLET,amount: lamports(100_000_000n) // 0.1 SOL});// Build a base64-encoded transactionconst transaction = merchant.pay.buildTransaction(buyerAddress, [instruction]);res.status(200).json({transaction,message: "Thanks for your purchase!"});} catch (error) {console.error("Transaction creation error:", error);res.status(500).json({ error: "Failed to create transaction" });}}
3. Crear URL de Pago
Genera la URL de solicitud de transacción:
import { encodeURL } from "@solana/pay";// Your API endpointconst apiUrl = "https://yoursite.com/api/pay";// Create transaction request URLconst url = encodeURL({link: new URL(apiUrl)});console.log(url.toString());// Output: solana:https://yoursite.com/api/pay
Del Lado de la Billetera: Obtener Solicitudes de Transacción
Del lado de la billetera, utiliza fetchTransaction para recuperar y firmar una
transacción desde una URL de solicitud de transacción:
import { createWalletClient } from "@solana/pay";const wallet = createWalletClient({rpcUrl: "https://api.mainnet-beta.solana.com",payer: myWalletSigner});// Parse the Solana Pay URLconst parsed = wallet.pay.parseURL(url);if ("link" in parsed) {// This is a transaction request — fetch the transaction from the serverconst transaction = await wallet.pay.fetchTransaction(myWalletSigner.address,parsed.link);// Sign and send the transaction...}
Ejemplos Avanzados
Pago con Token SPL
Crea una transacción para un pago en USDC:
import { address } from "@solana/kit";import {fetchMint,findAssociatedTokenPda,getTransferCheckedInstruction,TOKEN_PROGRAM_ADDRESS} from "@solana-program/token";async function createTokenPaymentInstruction(rpc, buyer, amount) {const USDC_MINT = address("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");// Get mint info for decimalsconst mint = await fetchMint(rpc, USDC_MINT);// Derive associated token accountsconst [buyerATA] = await findAssociatedTokenPda({owner: buyer,tokenProgram: TOKEN_PROGRAM_ADDRESS,mint: USDC_MINT});const [merchantATA] = await findAssociatedTokenPda({owner: MERCHANT_WALLET,tokenProgram: TOKEN_PROGRAM_ADDRESS,mint: USDC_MINT});// Calculate token amount with decimalsconst tokenAmount = BigInt(Math.round(amount * 10 ** mint.data.decimals));return getTransferCheckedInstruction({source: buyerATA,mint: USDC_MINT,destination: merchantATA,authority: buyer,amount: tokenAmount,decimals: mint.data.decimals});}
Precios Dinámicos con Descuentos
Implementa precios dinámicos basados en datos del cliente:
async function handlePost(req, res) {const { account } = req.body;const buyer = address(account);// Check if customer holds discount NFTconst hasDiscountNFT = await checkForDiscountNFT(buyer);// Calculate dynamic pricelet price = 10; // $10 base priceif (hasDiscountNFT) {price = price * 0.8; // 20% discount}const merchant = createMerchantClient({rpcUrl: process.env.SOLANA_RPC_URL});const instruction = await createTokenPaymentInstruction(merchant.rpc,buyer,price);const transaction = merchant.pay.buildTransaction(buyer, [instruction]);const message = hasDiscountNFT? `20% discount applied! Total: $${price.toFixed(2)}`: `Total: $${price.toFixed(2)}`;res.status(200).json({ transaction, message });}
Integración de Gestión de Pedidos
Integra con tu sistema de pedidos existente:
import { address, AccountRole, createNoopSigner, lamports } from "@solana/kit";// Enhanced POST handler with order managementasync function handlePost(req, res) {const { account } = req.body;const { orderId } = req.query;if (!orderId) {return res.status(400).json({ error: "Missing order ID" });}// Get order detailsconst order = await db.orders.findById(orderId);if (!order || order.status !== "pending") {return res.status(400).json({ error: "Invalid order" });}const buyer = address(account);// Create payment instruction (createNoopSigner because the wallet signs later)const instruction = getTransferSolInstruction({source: createNoopSigner(buyer),destination: MERCHANT_WALLET,amount: lamports(BigInt(Math.round(order.total * 1e9)))});// Add reference for tracking (accounts is readonly, so spread into a new instruction)const withRef = {...instruction,accounts: [...(instruction.accounts ?? []),{address: address(order.referenceKey),role: AccountRole.READONLY}]};const merchant = createMerchantClient({ rpcUrl: process.env.SOLANA_RPC_URL });const transaction = merchant.pay.buildTransaction(buyer, [withRef]);// Update order statusawait db.orders.update(orderId, {status: "processing",walletAddress: account});res.status(200).json({transaction,message: `Order ${order.id} - ${order.items.length} items`});}
Validación de Pagos
Valida las transacciones completadas:
import { address } from "@solana/kit";import { createMerchantClient } from "@solana/pay";const merchant = createMerchantClient({rpcUrl: "https://api.mainnet-beta.solana.com"});async function validatePayment(reference, recipient, amount) {try {const found = await merchant.pay.findReference(reference);// findReference only locates a signature mentioning the reference address.// validateTransfer checks recipient, amount, and token match.await merchant.pay.validateTransfer(found.signature, {recipient,amount,reference});return {success: true,signature: found.signature};} catch (error) {console.error("Payment validation error:", error);}return { success: false };}
Manejo de Errores
Implementa un manejo robusto de errores:
async function handlePost(req, res) {try {const { account } = req.body;if (!account) {return res.status(400).json({error: "Missing account parameter",code: "MISSING_ACCOUNT"});}let buyer;try {buyer = address(account);} catch {return res.status(400).json({error: "Invalid account address",code: "INVALID_ACCOUNT"});}const instruction = getTransferSolInstruction({source: createNoopSigner(buyer),destination: MERCHANT_WALLET,amount: lamports(100_000_000n)});const merchant = createMerchantClient({rpcUrl: process.env.SOLANA_RPC_URL});const transaction = merchant.pay.buildTransaction(buyer, [instruction]);res.status(200).json({transaction,message: "Payment ready"});} catch (error) {console.error("Transaction request error:", error);res.status(500).json({error: "Internal server error",code: "INTERNAL_ERROR"});}}
Mejores Prácticas
Seguridad
- Valida todos los parámetros de entrada
- Calcula los montos únicamente del lado del servidor
- Usa HTTPS para todos los endpoints
- Implementa limitación de tasa
- Almacena los datos sensibles de forma segura
Rendimiento
- Almacena en caché los datos de acceso frecuente
- Usa agrupación de conexiones
- Implementa tiempos de espera para las solicitudes
- Considera usar webhooks para la confirmación de pagos
Experiencia de Usuario
- Proporciona mensajes de error claros
- Muestra el progreso de la transacción
- Maneja los problemas de red de manera elegante
- Soporta reintentos de transacción
Is this page helpful?