You'll end up with a React component that shows a Solana Pay QR code for a USDC
payment, waits for the payment to land, and checks that it matches what you
asked for. You need Node.js 20+, a React or Next.js app (a fresh
create-next-app is fine), and a mobile wallet set to devnet. Fund it with
devnet USDC from faucet.circle.com and devnet SOL
from faucet.solana.com to cover transaction fees.
@solana/pay is currently in beta. APIs may change before the stable release.
1. Install Solana Pay
pnpm add @solana/pay@beta @solana/kit
2. Add the payment QR code
The component creates a unique reference for the order, encodes it into a
transfer request URL, and
renders the URL as a QR code. It then watches for a transaction that includes
the reference and validates the recipient, amount, and token before marking the
order paid. Set MERCHANT_WALLET to an address you control:
"use client";import { useEffect, useRef, useState } from "react";import {address,createSolanaRpc,createSolanaRpcSubscriptions,generateKeyPair,getAddressFromPublicKey} from "@solana/kit";import {createQR,encodeURL,validateTransfer,watchReference} from "@solana/pay";const MERCHANT_WALLET = address("your-wallet-address");const USDC_DEVNET = address("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU");const AMOUNT = 1;const rpc = createSolanaRpc("https://api.devnet.solana.com");const rpcSubscriptions = createSolanaRpcSubscriptions("wss://api.devnet.solana.com");export function Checkout() {const qrRef = useRef<HTMLDivElement>(null);const [signature, setSignature] = useState<string | null>(null);useEffect(() => {const abortController = new AbortController();async function startPayment() {const keyPair = await generateKeyPair();const reference = await getAddressFromPublicKey(keyPair.publicKey);const url = encodeURL({recipient: MERCHANT_WALLET,amount: AMOUNT,splToken: USDC_DEVNET,reference,label: "My Store",message: "Order #1001"});if (abortController.signal.aborted) return;if (qrRef.current) {qrRef.current.innerHTML = "";createQR(url, 300).append(qrRef.current);}const result = await watchReference(rpcSubscriptions, reference, {abortSignal: abortController.signal});if (result.err) throw new Error("Payment transaction failed");await validateTransfer(rpc, result.signature, {recipient: MERCHANT_WALLET,amount: AMOUNT,splToken: USDC_DEVNET,reference});setSignature(result.signature);}startPayment().catch((error) => {if (!abortController.signal.aborted) console.error(error);});return () => abortController.abort();}, []);if (signature) return <p>Payment confirmed: {signature}</p>;return <div ref={qrRef} />;}
amount is in token units, so 1 requests 1 USDC. Leave out splToken to
request SOL instead.
3. Take a payment
Run your app, scan the QR code with your wallet, and approve the payment. Once the transaction confirms and passes validation, the component shows the transaction signature. That signature is your receipt: look it up with getTransaction or in any explorer.
Validation in the browser is fine for a demo. Before you fulfill real orders, validate payments on your server. See verify payments.
Where to go next
- Solana Pay - transfer requests, transaction requests, and QR code options
- How payments work on Solana - wallets, stablecoins, token accounts, and fees
- Verify payments - confirm a payment server-side before fulfilling an order
- Send payments - payouts and transfers from your own backend
- Production readiness - before real money moves
Is this page helpful?