Permintaan transaksi memungkinkan alur pembayaran interaktif dengan mengizinkan dompet untuk berkomunikasi dengan server Anda untuk menyusun transaksi Solana apa pun. Ini membuka kasus penggunaan lanjutan seperti pencetakan NFT, penetapan harga dinamis, transaksi DeFi multi-langkah, dan logika bisnis kustom.
Gambaran Umum
Permintaan transaksi mengikuti format URL ini:
solana:<server-endpoint-url>
Dompet membuat dua permintaan HTTP ke endpoint Anda:
- Permintaan GET - Mengambil informasi pembayaran (label, ikon)
- Permintaan POST - Mendapatkan transaksi untuk ditandatangani
Pengaturan Dasar
1. Instal Dependensi
pnpm add @solana/pay@beta @solana/kit @solana-program/system \@solana/kit-plugin-rpc @solana/kit-plugin-payer @solana/kit-plugin-instruction-plan
2. Buat Endpoint API
Siapkan endpoint API yang menangani permintaan GET dan 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. Buat URL Pembayaran
Hasilkan URL permintaan transaksi:
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
Sisi Dompet: Mengambil Permintaan Transaksi
Di sisi dompet, gunakan fetchTransaction untuk mengambil dan menandatangani
transaksi dari URL permintaan transaksi:
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...}
Contoh Lanjutan
Pembayaran Token SPL
Buat transaksi untuk pembayaran 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});}
Penetapan Harga Dinamis dengan Diskon
Implementasikan penetapan harga dinamis berdasarkan data pelanggan:
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 });}
Integrasi Manajemen Pesanan
Integrasikan dengan sistem pesanan yang sudah ada:
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`});}
Validasi Pembayaran
Validasi transaksi yang telah selesai:
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 };}
Penanganan Error
Implementasikan penanganan error yang kuat:
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"});}}
Praktik Terbaik
Keamanan
- Validasi semua parameter input
- Hitung jumlah hanya di sisi server
- Gunakan HTTPS untuk semua endpoint
- Implementasikan pembatasan laju permintaan
- Simpan data sensitif dengan aman
Performa
- Cache data yang sering diakses
- Gunakan connection pooling
- Implementasikan timeout permintaan
- Pertimbangkan menggunakan webhook untuk konfirmasi pembayaran
Pengalaman Pengguna
- Berikan pesan error yang jelas
- Tampilkan progres transaksi
- Tangani masalah jaringan dengan baik
- Dukung pengulangan transaksi
Is this page helpful?