USDC on Base is a fully-backed digital dollar that settles in seconds and costs pennies in gas. Base Pay lets you accept those dollars with a single click—no cards, no FX fees, no chargebacks.
Any user can pay – works with every Base Account (smart-wallet) out of the box.
USDC, not gas – you charge in dollars; gas sponsorship is handled automatically.
Fast – most payments confirm in <2 seconds on Base.
Funded accounts – users pay with USDC from their Base Account or Coinbase Account.
No extra fees – you receive the full amount.
Please Follow the Brand GuidelinesIf you intend on using the BasePayButton, please follow the Brand Guidelines to ensure consistency across your application.
import { pay, getPaymentStatus } from '@base-org/account';// Trigger a payment – user will see a popup from their wallet servicetry { const payment = await pay({ amount: '1.00', // USD amount (USDC used internally) to: '0xRecipient', // your address testnet: true // set false for Mainnet }); // Option 1: Poll until mined const { status } = await getPaymentStatus({ id: payment.id, testnet: true // MUST match the testnet setting used in pay() }); if (status === 'completed') console.log('🎉 payment settled');} catch (error) { console.error(`Payment failed: ${error.message}`);}
Important: The testnet parameter in getPaymentStatus() must match the value used in the original pay() call. If you initiated a payment on testnet with testnet: true, you must also pass testnet: true when checking its status.
This is what the user will see when prompted to pay:
Required by default — set optional: true to avoid aborting the payment if the user declines.
How to validate the user’s information?You can use the callbackURL to validate the user’s information on the server side.Learn more about this in the callbackURL reference.
When accepting payments, your backend must validate transactions and user info received from the frontend. This section covers two critical aspects: verifying transaction completion and validating user information.
Use getPaymentStatus() on your backend to confirm that a payment has been completed before fulfilling orders. Never trust payment confirmations from the frontend alone.
Backend (SDK)
Report incorrect code
Copy
Ask AI
import { getPaymentStatus } from '@base-org/account';export async function checkPayment(txId: string, testnet = false) { const status = await getPaymentStatus({ id: txId, testnet // Must match the testnet setting from the original pay() call }); if (status.status === 'completed') { // fulfill order }}
Prevent Replay AttacksA malicious user could submit the same valid transaction ID multiple times to receive goods or services repeatedly. Always track processed transaction IDs in your database.
Here’s an example that prevents replay attacks by storing processed transactions:
Backend (with replay protection)
Report incorrect code
Copy
Ask AI
import { getPaymentStatus } from '@base-org/account';// Example using a database to track processed transactions// Replace with your actual database implementation (PostgreSQL, MongoDB, etc.)const processedTransactions = new Map<string, { orderId: string; sender: string; amount: string; timestamp: Date;}>(); // In production, use a persistent databaseexport async function verifyAndFulfillPayment( txId: string, orderId: string, testnet = false) { // 1. Check if this transaction was already processed if (processedTransactions.has(txId)) { throw new Error('Transaction already processed'); } // 2. Verify the payment status on-chain const { status, sender, amount, recipient } = await getPaymentStatus({ id: txId, testnet }); if (status !== 'completed') { throw new Error(`Payment not completed. Status: ${status}`); } // 3. Validate the payment details match your order // This ensures the user paid the correct amount to the correct address const expectedAmount = await getOrderAmount(orderId); const expectedRecipient = process.env.PAYMENT_ADDRESS; if (amount !== expectedAmount) { throw new Error('Payment amount mismatch'); } if (recipient.toLowerCase() !== expectedRecipient.toLowerCase()) { throw new Error('Payment recipient mismatch'); } // 4. Mark transaction as processed BEFORE fulfilling // Store sender for easy lookup (e.g., to query all payments from a user) // In production, use a database transaction to ensure atomicity processedTransactions.set(txId, { orderId, sender, amount, timestamp: new Date() }); // 5. Fulfill the order await fulfillOrder(orderId); return { success: true, orderId, sender };}
Database recommendations for tracking transactions:
Store the transaction ID, order ID, sender address, amount, timestamp, and fulfillment status
Use a unique constraint on the transaction ID to prevent duplicates
Consider adding an index on the transaction ID for fast lookups
If you’re collecting user information (email, phone, shipping address) during checkout, use the callbackURL parameter to validate this data server-side before the transaction is submitted.Your callback endpoint receives the user’s information and must respond with either a success or error response:
Backend (validation endpoint)
Report incorrect code
Copy
Ask AI
export async function POST(request: Request) { const requestData = await request.json(); const { requestedInfo } = requestData.capabilities.dataCallback; const errors: Record<string, string> = {}; // Validate email if (requestedInfo.email) { const blockedDomains = ['tempmail.com', 'throwaway.com']; const domain = requestedInfo.email.split('@')[1]; if (blockedDomains.includes(domain)) { errors.email = 'Please use a valid email address'; } } // Validate shipping address if (requestedInfo.physicalAddress) { const addr = requestedInfo.physicalAddress; const supportedCountries = ['US', 'CA', 'GB']; if (!supportedCountries.includes(addr.countryCode)) { errors.physicalAddress = { countryCode: 'We currently only ship to US, Canada, and UK' }; } } // Return errors if validation failed if (Object.keys(errors).length > 0) { return Response.json({ errors }); } // Success - return the request to proceed with the transaction return Response.json({ request: requestData });}
The callback is invoked before the transaction is submitted. If you return errors, the user is prompted to correct their information. If you return success, the transaction proceeds.
For complete details on the callback request/response format and all supported data types, see the dataCallback reference.
Please Follow the Brand GuidelinesIf you intend on using the BasePayButton, please follow the Brand Guidelines to ensure consistency across your application.