Simplified payment flow using the useTonPay React hook
The useTonPay hook provides a streamlined interface for sending TON Pay transfers in React applications. It handles wallet connection, transaction signing, and error management automatically, abstracting the complexity of TonConnect integration.
Build the payment message directly in the browser. This approach is faster to implement and reduces server load, but requires the client to handle all transaction details.Best suited for:
Applications without backend infrastructure
Rapid prototyping and development
Simple payment flows without complex business logic
Delegate message creation to your backend server. The server builds the transaction, stores tracking identifiers, and returns the message to the client for signing.Best suited for:
Production applications requiring audit trails
Complex payment workflows with validation
Centralized logging and transaction management
Applications where tracking identifiers must be persisted before user action
Ensure your application is wrapped with the TonConnect UI provider:
Copy
import { TonConnectUIProvider } from "@tonconnect/ui-react";export function App() { return ( <TonConnectUIProvider manifestUrl="/tonconnect-manifest.json"> {/* Your application components */} </TonConnectUIProvider> );}
The TonConnect manifest file must be publicly accessible and properly
configured with your application’s metadata. See TonConnect documentation for
manifest requirements.
Create an API endpoint that builds the transaction message and stores tracking data:
Copy
import { createTonPayTransfer } from "@ton-pay/api";app.post("/api/create-payment", async (req, res) => { const { amount, senderAddr, orderId } = req.body; try { const { message, reference, bodyBase64Hash } = await createTonPayTransfer( { amount, asset: "TON", recipientAddr: "EQ........", senderAddr, commentToSender: `Payment for Order ${orderId}`, commentToRecipient: `Order ${orderId}`, }, { chain: "testnet", apiKey: "yourTonPayApiKey", } ); // Store tracking identifiers in your database await db.createPayment({ orderId, reference, bodyBase64Hash, amount, asset: "TON", status: "pending", senderAddr, }); // Return only the message to the client res.json({ message }); } catch (error) { console.error("Failed to create payment:", error); res.status(500).json({ error: "Failed to create payment" }); }});
Always persist tracking identifiers (reference and bodyBase64Hash) in your
database before returning the message to the client. If the client loses
connection or closes the browser, you still need these identifiers to process
incoming webhooks.
try { await pay(getMessage);} catch (error) { // User rejected the transaction in their wallet if (error.message?.includes("rejected")) { console.log("User rejected the transaction"); } // Network or API errors else if (error.message?.includes("Failed to create TON Pay transfer")) { console.log("API error - check your configuration"); } // Other unexpected errors else { console.error("Unexpected error:", error); }}
The reference and bodyBase64Hash returned by createTonPayTransfer are essential for matching webhook notifications to your orders. Store these immediately in your database, preferably before the user signs the transaction.
Copy
// Good: Store before transactionconst { message, reference } = await createTonPayTransfer(...);await db.createPayment({ reference, status: "pending" });return { message };// Bad: Only storing after successful transactionconst { txResult, reference } = await pay(...);await db.createPayment({ reference }); // Too late if network fails
Validate amounts on the server
Never trust amount values sent from the client. Always validate or generate amounts server-side to prevent manipulation.
Copy
// Server-side endpointapp.post("/api/create-payment", async (req, res) => { const { orderId, senderAddr } = req.body; // Fetch the actual amount from your database const order = await db.getOrder(orderId); // Use the verified amount, not req.body.amount const { message } = await createTonPayTransfer({ amount: order.amount, asset: order.currency, recipientAddr: "EQC...yourWalletAddress", senderAddr, }); res.json({ message });});
Implement proper error boundaries
Wrap your payment components with React error boundaries to gracefully handle failures.
Copy
class PaymentErrorBoundary extends React.Component { componentDidCatch(error: Error) { console.error("Payment component error:", error); // Log to your error tracking service } render() { return this.props.children; }}
Show loading states
Payment processing can take several seconds. Provide clear feedback to users during wallet connection and transaction signing.
Verify the TonConnect manifest URL is accessible and valid - Check browser
console for TonConnect initialization errors - Ensure the manifest file is
served with correct CORS headers - Test the manifest URL directly in your
browser
Transaction fails with 'Invalid recipient address'
Verify the recipient address is a valid TON address (starts with EQ, UQ, or
0:) - Ensure you’re using the correct address format for your chain (mainnet
vs testnet) - Check that the address includes the full base64 format with
workchain
Factory function throws 'Failed to create TON Pay transfer'
Common causes:
Invalid API key or missing API key for authenticated endpoints
The TON Pay API key is optional but enables essential merchant features including transaction visibility in the dashboard, webhook notifications, and centralized wallet management.When using useTonPay with server-side message building, optionally include the API key in your backend endpoint:
For complete testnet configuration guide, including how to get testnet TON,
test jetton transfers, verify transactions, and transition to production, see
the Testnet Configuration
section.