Solana Payment Acceptance Setup
The specificity of Solana payments is that there's no native "payment request" standard like BIP-21 (Bitcoin) or EIP-681 (Ethereum). But there is Solana Pay — an open protocol developed by Solana Labs with the ecosystem, covering most payment use cases: simple SOL transfers, SPL tokens, transaction requests with arbitrary backend logic.
Solana Pay: Transfer Request
Simplest scenario — request to transfer a fixed amount of SOL or SPL token. URL scheme:
solana:<recipient>?amount=<value>&label=<label>&message=<message>&memo=<memo>
Example for paying 10 USDC:
solana:7Np41RSSZFkBmBUkVBntbRMzEnbZBqVkjFEf3BQnkGTi
?amount=10
&spl-token=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
&label=MyShop
&message=Order%20%2312345
&memo=ORDER-12345
spl-token — USDC mint address on mainnet. memo — arbitrary string, written to on-chain transaction via Memo program, used for order reconciliation.
This URL is encoded in a QR code. Wallets supporting Solana Pay (Phantom, Solflare, Backpack) automatically parse it and show payment details.
QR Generation on Backend
import {
encodeURL,
createQR,
TransferRequestURLFields,
} from "@solana/pay";
import { PublicKey } from "@solana/web3.js";
import BigNumber from "bignumber.js";
function createPaymentRequest(orderId: string, amountUSDC: number) {
const recipient = new PublicKey("YOUR_MERCHANT_WALLET");
const splToken = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
const urlFields: TransferRequestURLFields = {
recipient,
amount: new BigNumber(amountUSDC),
splToken,
label: "MyShop",
message: `Order #${orderId}`,
memo: orderId,
};
const url = encodeURL(urlFields);
const qrCode = createQR(url, 360, "transparent");
return { url: url.toString(), qrCode };
}
Payment Verification
After showing QR need to confirm payment. Solana Pay provides findReference — searching transaction by reference pubkey (unique key added to each payment):
import { findReference, validateTransfer } from "@solana/pay";
import { Connection, clusterApiUrl, Keypair } from "@solana/web3.js";
const connection = new Connection(clusterApiUrl("mainnet-beta"), "confirmed");
// reference — unique keypair for each order
const reference = Keypair.generate().publicKey;
// Poll every 2 seconds
async function pollForPayment(reference: PublicKey, expectedAmount: BigNumber) {
while (true) {
try {
const signatureInfo = await findReference(connection, reference, {
finality: "confirmed",
});
await validateTransfer(connection, signatureInfo.signature, {
recipient: MERCHANT_WALLET,
amount: expectedAmount,
splToken: USDC_MINT,
reference,
});
return signatureInfo.signature; // payment confirmed
} catch (e) {
// FindReferenceError — transaction hasn't appeared yet, waiting
await new Promise((r) => setTimeout(r, 2000));
}
}
}
validateTransfer checks: correct recipient, correct amount, correct token. If something doesn't match — throws exception. This protects against payment with "similar" transaction.
Finality in Solana
Solana has no probabilistic finality like Bitcoin. Here are the levels:
-
processed— transaction included in block, may be rollback on fork -
confirmed— block received >66% supermajority votes -
finalized— block in rooted part of chain, rollback impossible
For payments use confirmed — balance of speed (~2–3 sec) and security. finalized adds another ~5–10 sec, justified only for large amounts.
What's Needed for Full Implementation
- Merchant wallet (better hardware wallet or multisig via Squads Protocol)
- Endpoint creating payment request with unique reference
- WebSocket or polling to monitor confirmation
- Saving transaction
signaturein database for audit - Timeout handling (Solana Pay recommends 2 min, after — show error)
- RPC endpoint: not public
api.mainnet-beta.solana.comfor production — it's throttled; use Helius, QuickNode or Alchemy







