Build the wallet payment
Construct the USDC transaction from the checkout response.
Your server creates the checkout. Your frontend receives only its public payment details and asks the buyer's wallet to approve the transaction. The partner API key never reaches the browser.
Use the checkout's payment details
| Value | Use |
|---|---|
payment.treasury_ata | Destination token account |
payment.usdc_mint | Token mint to transfer |
payment.decimals | Token precision |
payment.memo | Exact memo for this checkout; use verbatim |
total_usd | Agreed total, not the per-ticket price |
If payment is null, do not build a Verity-collected transfer. Confirm your platform's payment mode with Verity.
Build an unsigned transaction
The example below uses @solana/web3.js and @solana/spl-token. Supply your application's Solana connection and the connected buyer's public key.
import { Buffer } from 'buffer';
import { PublicKey, Transaction, TransactionInstruction } from '@solana/web3.js';
import { createTransferCheckedInstruction, getAssociatedTokenAddressSync } from '@solana/spl-token';
function decimalToBaseUnits(value: string, decimals: number): bigint {
const match = /^(\d+)(?:\.(\d+))?$/.exec(value);
if (!match || !Number.isInteger(decimals) || decimals < 0 || decimals > 9) {
throw new Error('Invalid token amount');
}
const fraction = match[2] ?? '';
if (fraction.length > decimals && /[1-9]/.test(fraction.slice(decimals))) {
throw new Error('Amount exceeds token precision');
}
return BigInt(match[1]) * 10n ** BigInt(decimals)
+ BigInt(fraction.slice(0, decimals).padEnd(decimals, '0') || '0');
}
// checkout is the data returned by your server after creating a checkout.
// Only continue while awaiting_payment, before payment_deadline,
// and if this buyer has not already submitted a payment for it.
const { treasury_ata, usdc_mint, decimals, memo } = checkout.payment;
const mint = new PublicKey(usdc_mint);
const amount = decimalToBaseUnits(String(checkout.total_usd), decimals);
const { blockhash } = await connection.getLatestBlockhash();
const transaction = new Transaction({
recentBlockhash: blockhash,
feePayer: buyerPublicKey,
}).add(
createTransferCheckedInstruction(
getAssociatedTokenAddressSync(mint, buyerPublicKey),
mint,
new PublicKey(treasury_ata),
buyerPublicKey,
amount,
decimals,
),
new TransactionInstruction({
keys: [],
programId: new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr'),
data: Buffer.from(memo, 'utf8'),
}),
);
// Ask your connected wallet to sign and submit transaction.
// Send the signature to your server immediately after submission.
// Your server registers it with the checkout payment endpoint.The buyer needs the quoted USDC and enough SOL for transaction fees. Use a single-signer wallet; multisig payments can require manual support if a refund is needed.
Four rules to check
- Use
transferChecked, not the uncheckedtransferinstruction. - Transfer exactly the quoted amount with the returned mint and destination.
- Include exactly one memo, using the returned
payment.memowithout alteration. - Register the signature promptly after submission, before
payment_deadline.
Do not multiply total_usd by quantity again. Do not build the amount with floating-point multiplication. Do not use the shorter memo from the existing payment-first flow.
Preserve an uncertain payment
Keep the checkout ID and transaction signature even if the wallet or network request reports a timeout. Query the same checkout before attempting another payment.