Quickstart: accept a payment

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

Is this page helpful?

Table of Contents

Edit Page
© 2026 Solana Foundation. All rights reserved.
Quickstart: accept a payment | Solana