diff --git a/ecosystem/ton-pay/quick-start.mdx b/ecosystem/ton-pay/quick-start.mdx index ca3d01d9a..94aa134f4 100644 --- a/ecosystem/ton-pay/quick-start.mdx +++ b/ecosystem/ton-pay/quick-start.mdx @@ -7,7 +7,7 @@ import { Aside } from '/snippets/aside.jsx'; ## TON Connect manifest -Before installing and setting up the TON Pay SDK, the application must provide a TON Connect manifest, which is a JSON file that defines application metadata. Wallets use [this manifest](/ecosystem/ton-connect/manifest) to discover the application. +Before installing and setting up the TON Pay SDK, the application must provide a TON Connect manifest, which is a JSON file that defines application metadata. Wallets use the [TON Connect manifest](/ecosystem/ton-connect/manifest) to discover the application. ## First payment @@ -73,35 +73,44 @@ Before installing and setting up the TON Pay SDK, the application must provide a - Add a `TonPayButton` and provide a handler. The handler uses `useTonPay` to connect a wallet if needed, send a transaction through TON Connect, and track the transaction result. + Add a `TonPayButton` and provide a handler. The handler uses `useTonPay` to connect a wallet if needed, send a transaction through TON Connect, and return tracking data for the next step. - `TonPayButton` wraps wallet connect and disconnect UX and invokes the provided handler. - `useTonPay` accepts an async message factory that receives `senderAddr` and returns `{ message }` along with any tracking fields to propagate. + - Return `reference` from `createTonPayTransfer` so it can be used later with `getTonPayTransferByReference`. The returned `{ message }` is a TON Connect transaction message. `useTonPay` forwards it to the wallet through TON Connect and initiates the transaction send; direct calls to the wallet SDK are not required. + In the examples below, replace `` with the recipient wallet address, `` with an optional dashboard API key, and `` with an order label or ID. + ```tsx import { TonPayButton, useTonPay } from "@ton-pay/ui-react"; - import { useTonConnectUI } from "@tonconnect/ui-react"; import { createTonPayTransfer } from "@ton-pay/api"; + const recipientAddr = ""; + const orderReference = ""; + + // Set chain to "mainnet" in production. + const options = { + chain: "testnet", + + // Pass an API key from the dashboard when available. + apiKey: "", + } as const; + export default function PayButton() { const { pay } = useTonPay(); - const [tonConnectUI] = useTonConnectUI(); async function createMessage(senderAddr: string) { const { message, reference } = await createTonPayTransfer( { amount: 12.34, asset: "TON", - recipientAddr: "", + recipientAddr, senderAddr, - commentToSender: "", + commentToSender: orderReference, }, - { - chain: "testnet", // use "mainnet" for production - apiKey: "" // optional - } + options ); return { message, reference }; } @@ -116,46 +125,54 @@ Before installing and setting up the TON Pay SDK, the application must provide a type="danger" title="Client-only processing is fragile" > - Users can close the tab before transaction results are persisted, which breaks tracking and confirmation logic. Build messages and track transactions on the [server side](/ecosystem/ton-pay/payment-integration/payments-react). + Users can close the tab before transaction results are persisted, which breaks tracking and confirmation logic. Build messages and track transactions in the [Send payments using React](/ecosystem/ton-pay/payment-integration/payments-react) guide. ```ts // Backend: POST /api/create-payment - import { createTonPayTransfer } from "@ton-pay/api"; - import { TON } from "@ton-pay/api"; + import { createTonPayTransfer, TON } from "@ton-pay/api"; + + const recipientAddr = ""; + + // Set chain to "mainnet" in production. + const options = { + chain: "testnet", + + // Pass an API key from the dashboard when available. + apiKey: "", + } as const; app.post("/api/create-payment", async (req, res) => { - const { productId, senderAddr } = req.body; + const { productId, senderAddr } = req.body; - // Use the backend to create an order and calculate the amount using productId price - const amount = 12.23; // order amount - const orderId = 1; // ID of a new order + // Create an order and calculate the amount from the product price. + const amount = 12.23; + const orderId = 1; - // 1) Create transfer + get tracking identifiers - const { message, reference, bodyBase64Hash } = await createTonPayTransfer( - { amount, asset: TON, recipientAddr: "", senderAddr }, - { chain: "testnet", apiKey: "" } // API key is optional - ); + // Create the transfer and get tracking identifiers. + const { message, reference, bodyBase64Hash } = await createTonPayTransfer( + { amount, asset: TON, recipientAddr, senderAddr }, + options + ); - // 2) Persist identifiers the DB (orderId, reference, bodyBase64Hash) immediately + // Persist identifiers in the database immediately. - // 3) Return only the message to the client - res.json({ message }); + // Return only the message to the client. + res.json({ message }); }); - ``` - ```typescript + ```tsx // Frontend import { TonPayButton, useTonPay } from "@ton-pay/ui-react"; - export function PayOrder({ productId }: { productId: any }) { + export function PayOrder({ productId }: { productId: string }) { const { pay } = useTonPay(); async function createMessage(senderAddr: string) { @@ -175,98 +192,149 @@ Before installing and setting up the TON Pay SDK, the application must provide a + - + Wallet approval does not mean the payment is finalized yet. After `pay(createMessage)` resolves, use the `reference` returned by `createTonPayTransfer` to query TON Pay until the transfer leaves the `pending` state. + The example below uses React. The same flow applies in other clients: keep the `reference`, poll status, and update the UI when the transfer leaves `pending`. + + Use the SDK flow below: + + 1. Return `reference` from the `createMessage` function together with `message`. + 1. Call `pay(createMessage)` and, once it resolves, read the propagated `reference` from its return value. + 1. Call `getTonPayTransferByReference(reference, options)` with the same `chain` and optional `apiKey` used during transfer creation. + 1. While the SDK returns `status: "pending"`, keep the UI in a loading or "confirming payment" state. + 1. When the status becomes `success`, show the confirmation UI and persist any result fields the application needs, such as `txHash` or `traceId`. + 1. When the status becomes `error`, show the failure state and capture `errorCode` or `errorMessage` for diagnostics. + + In a client-only flow, persist the `reference` after `pay(createMessage)` resolves and returns it. This is enough to resume status checks after a reload during polling, but it does not cover the period while the wallet approval screen is open. To avoid that gap, create the transfer on the server and persist the `reference` before opening the wallet. + + - ```tsx - import { useState } from "react"; - import { TonPayButton, useTonPay } from "@ton-pay/ui-react"; - import { createTonPayTransfer, getTonPayTransferByReference, type CompletedTonPayTransferInfo } from "@ton-pay/api"; - - export default function Checkout() { - const { pay } = useTonPay(); - const [loading, setLoading] = useState(false); - const [reference, setReference] = useState(null); - const [result, setResult] = useState(null); - - const options = { chain: "testnet", apiKey: "yourTonPayApiKey" } as const; // API key is optional - - async function createMessage(senderAddr: string) { - const { message, reference } = await createTonPayTransfer( - { amount: 12.34, asset: "TON", recipientAddr: "EQC...yourWalletAddress", senderAddr }, - options - ); - setReference(reference); - return { message, reference }; - } - - async function handlePay() { - setLoading(true); - try { - const { reference } = await pay(createMessage); - // Poll status until success - for (;;) { - try { - const t = await getTonPayTransferByReference(reference, options); - if (t.status === "success") { - setResult(t); - break; - } - } catch {} - await new Promise(r => setTimeout(r, 1000)); - } - } finally { - setLoading(false); - } - } - - return ( - <> - - - {reference && !result &&
Payment sent. Reference: {reference}
} - {result &&
Payment successful! Tx hash: {result.txHash}
} - {/* If a transaction hash is available, render an explorer link: - - */} - - ); + ```tsx title="Result handling example" expandable + import { useState } from "react"; + import { TonPayButton, useTonPay } from "@ton-pay/ui-react"; + import { + createTonPayTransfer, + getTonPayTransferByReference, + type CompletedTonPayTransferInfo, + } from "@ton-pay/api"; + + type PaymentState = "idle" | "sending" | "pending" | "success" | "error"; + + const amount = 12.34; + const recipientAddr = ""; + + // Set chain to "mainnet" in production. + const options = { + chain: "testnet", + + // Pass an API key from the dashboard when available. + apiKey: "", + } as const; + + export default function Checkout() { + const { pay } = useTonPay(); + const [paymentState, setPaymentState] = useState("idle"); + const [reference, setReference] = useState(null); + const [result, setResult] = useState(null); + const [errorMessage, setErrorMessage] = useState(null); + + async function createMessage(senderAddr: string) { + const { message, reference } = await createTonPayTransfer( + { + amount, + asset: "TON", + recipientAddr, + senderAddr, + }, + options + ); + + setReference(reference); + return { message, reference }; + } + + // Polls TON Pay until the transfer gets a final status. + async function waitForTransferResult(reference: string) { + for (;;) { + const transfer = await getTonPayTransferByReference(reference, options); + + if (transfer.status === "pending") { + await new Promise((resolve) => setTimeout(resolve, 1000)); + continue; + } + + return transfer; } - ``` + } - ```tsx - import { useTonWallet } from "@tonconnect/ui-react"; - import { CHAIN } from "@tonconnect/ui"; - - function ExplorerLink({ txHash }: { txHash: string }) { - const wallet = useTonWallet(); - const isTestnet = wallet?.account?.chain === CHAIN.TESTNET; - const tonviewer = isTestnet - ? `https://testnet.tonviewer.com/transaction/${txHash}` - : `https://tonviewer.com/transaction/${txHash}`; - const tonscan = isTestnet - ? `https://testnet.tonscan.org/tx/${txHash}` - : `https://tonscan.org/tx/${txHash}`; - - return ( -
- TonViewer - {" • "} - Tonscan -
- ); + async function handlePay() { + setPaymentState("sending"); + setErrorMessage(null); + setReference(null); + setResult(null); + + try { + const { reference } = await pay(createMessage); + setPaymentState("pending"); + + const transfer = await waitForTransferResult(reference); + + if (transfer.status === "success") { + setResult(transfer); + setPaymentState("success"); + return; + } + + setPaymentState("error"); + setErrorMessage(transfer.errorMessage ?? "Payment failed"); + } catch (error) { + setPaymentState("error"); + setErrorMessage(error instanceof Error ? error.message : "Payment failed"); } - ``` - -
+ } + + return ( + <> + + + {paymentState === "pending" && reference && ( +
+ Payment submitted. Waiting for blockchain confirmation. Reference: {reference} +
+ )} + + {paymentState === "success" && result && ( +
+ Payment confirmed. Tx hash: {result.txHash} +
+ )} + + {paymentState === "error" && errorMessage && ( +
+ Payment failed: {errorMessage} +
+ )} + + ); + } + ``` + + The [status lookup guide](/ecosystem/ton-pay/payment-integration/status-info) describes the response fields and lookup methods used in this step. @@ -286,39 +354,47 @@ import { TonConnectUIProvider } from "@tonconnect/ui-react"; import { TonPayButton, useTonPay } from "@ton-pay/ui-react"; import { createTonPayTransfer } from "@ton-pay/api"; -function AppContent() { - const { pay } = useTonPay(); +const recipientAddr = ""; +const commentToSender = "Order #123"; - async function createMessage(senderAddr: string) { - const { message, reference } = await createTonPayTransfer( - { - amount: 12.34, - asset: "TON", - recipientAddr: "", - senderAddr, - commentToSender: "Order #123", - }, - { - chain: "testnet", - apiKey: "" // optional - } - ); - return { message, reference }; - } +// Set chain to "mainnet" in production. +const options = { + chain: "testnet", - return ( - pay(createMessage)} /> + // Pass an API key from the dashboard when available. + apiKey: "", +} as const; + +function AppContent() { + const { pay } = useTonPay(); + + async function createMessage(senderAddr: string) { + const { message, reference } = await createTonPayTransfer( + { + amount: 12.34, + asset: "TON", + recipientAddr, + senderAddr, + commentToSender, + }, + options ); + return { message, reference }; + } + + return ( + pay(createMessage)} /> + ); } export default function App() { - return ( - - - - ); + return ( + + + + ); } ```