> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ton.org/llms.txt
> Use this file to discover all available pages before exploring further.

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://docs.ton.org/feedback

```json
{
  "path": "/ecosystem/ton-pay/payment-integration/payments-react",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>

# How to send payments using TON Pay React hook

export const Aside = ({type = "note", title = "", icon = "", iconType = "regular", children}) => {
  const asideVariants = ["note", "tip", "caution", "danger"];
  const asideComponents = {
    note: {
      outerStyle: "border-sky-500/20 bg-sky-50/50 dark:border-sky-500/30 dark:bg-sky-500/10",
      innerStyle: "text-sky-900 dark:text-sky-200",
      calloutType: "note",
      icon: <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-sky-500" aria-label="Note">
          <path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.3C10.14 1.3 12.7 3.86 12.7 7C12.7 10.14 10.14 12.7 7 12.7C5.48908 12.6974 4.0408 12.096 2.97241 11.0276C1.90403 9.9592 1.30264 8.51092 1.3 7C1.3 3.86 3.86 1.3 7 1.3ZM7 0C3.14 0 0 3.14 0 7C0 10.86 3.14 14 7 14C10.86 14 14 10.86 14 7C14 3.14 10.86 0 7 0ZM8 3H6V8H8V3ZM8 9H6V11H8V9Z"></path>
        </svg>
    },
    tip: {
      outerStyle: "border-emerald-500/20 bg-emerald-50/50 dark:border-emerald-500/30 dark:bg-emerald-500/10",
      innerStyle: "text-emerald-900 dark:text-emerald-200",
      calloutType: "tip",
      icon: <svg width="11" height="14" viewBox="0 0 11 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="text-emerald-600 dark:text-emerald-400/80 w-3.5 h-auto" aria-label="Tip">
          <path d="M3.12794 12.4232C3.12794 12.5954 3.1776 12.7634 3.27244 12.907L3.74114 13.6095C3.88471 13.8248 4.21067 14 4.46964 14H6.15606C6.41415 14 6.74017 13.825 6.88373 13.6095L7.3508 12.9073C7.43114 12.7859 7.49705 12.569 7.49705 12.4232L7.50055 11.3513H3.12521L3.12794 12.4232ZM5.31288 0C2.52414 0.00875889 0.5 2.26889 0.5 4.78826C0.5 6.00188 0.949566 7.10829 1.69119 7.95492C2.14321 8.47011 2.84901 9.54727 3.11919 10.4557C3.12005 10.4625 3.12175 10.4698 3.12261 10.4771H7.50342C7.50427 10.4698 7.50598 10.463 7.50684 10.4557C7.77688 9.54727 8.48281 8.47011 8.93484 7.95492C9.67728 7.13181 10.1258 6.02703 10.1258 4.78826C10.1258 2.15486 7.9709 0.000106649 5.31288 0ZM7.94902 7.11267C7.52078 7.60079 6.99082 8.37878 6.6077 9.18794H4.02051C3.63739 8.37878 3.10743 7.60079 2.67947 7.11294C2.11997 6.47551 1.8126 5.63599 1.8126 4.78826C1.8126 3.09829 3.12794 1.31944 5.28827 1.3126C7.2435 1.3126 8.81315 2.88226 8.81315 4.78826C8.81315 5.63599 8.50688 6.47551 7.94902 7.11267ZM4.87534 2.18767C3.66939 2.18767 2.68767 3.16939 2.68767 4.37534C2.68767 4.61719 2.88336 4.81288 3.12521 4.81288C3.36705 4.81288 3.56274 4.61599 3.56274 4.37534C3.56274 3.6515 4.1515 3.06274 4.87534 3.06274C5.11719 3.06274 5.31288 2.86727 5.31288 2.62548C5.31288 2.38369 5.11599 2.18767 4.87534 2.18767Z"></path>
        </svg>
    },
    caution: {
      outerStyle: "border-amber-500/20 bg-amber-50/50 dark:border-amber-500/30 dark:bg-amber-500/10",
      innerStyle: "text-amber-900 dark:text-amber-200",
      calloutType: "warning",
      icon: <svg className="flex-none w-5 h-5 text-amber-400 dark:text-amber-300/80" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-label="Warning">
          <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
        </svg>
    },
    danger: {
      outerStyle: "border-red-500/20 bg-red-50/50 dark:border-red-500/30 dark:bg-red-500/10",
      innerStyle: "text-red-900 dark:text-red-200",
      calloutType: "danger",
      icon: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" className="text-red-600 dark:text-red-400/80 w-4 h-4" aria-label="Danger">
          <path d="M17.1 292c-12.9-22.3-12.9-49.7 0-72L105.4 67.1c12.9-22.3 36.6-36 62.4-36l176.6 0c25.7 0 49.5 13.7 62.4 36L494.9 220c12.9 22.3 12.9 49.7 0 72L406.6 444.9c-12.9 22.3-36.6 36-62.4 36l-176.6 0c-25.7 0-49.5-13.7-62.4-36L17.1 292zm41.6-48c-4.3 7.4-4.3 16.6 0 24l88.3 152.9c4.3 7.4 12.2 12 20.8 12l176.6 0c8.6 0 16.5-4.6 20.8-12L453.4 268c4.3-7.4 4.3-16.6 0-24L365.1 91.1c-4.3-7.4-12.2-12-20.8-12l-176.6 0c-8.6 0-16.5 4.6-20.8 12L58.6 244zM256 128c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"></path>
        </svg>
    }
  };
  let variant = type;
  let gotInvalidVariant = false;
  if (!asideVariants.includes(type)) {
    gotInvalidVariant = true;
    variant = "danger";
  }
  const iconVariants = ["regular", "solid", "light", "thin", "sharp-solid", "duotone", "brands"];
  if (!iconVariants.includes(iconType)) {
    iconType = "regular";
  }
  return <>
      <div className={`callout my-4 px-5 py-4 overflow-hidden rounded-2xl flex gap-3 border ${asideComponents[variant].outerStyle}`} data-callout-type={asideComponents[variant].calloutType}>
        <div className="mt-0.5 w-4" data-component-part="callout-icon">
          {}
          {icon === "" ? asideComponents[variant].icon : <Icon icon={icon} iconType={iconType} size={14} />}
        </div>
        <div className={`text-sm prose min-w-0 w-full ${asideComponents[variant].innerStyle}`} data-component-part="callout-content">
          {gotInvalidVariant ? <p>
              <span className="font-bold">
                Invalid <code>type</code> passed!
              </span>
              <br />
              <span className="font-bold">Received: </span>
              {type}
              <br />
              <span className="font-bold">Expected one of: </span>
              {asideVariants.join(", ")}
            </p> : <>
              {title && <p className="font-bold">{title}</p>}
              {children}
            </>}
        </div>
      </div>
    </>;
};

The `useTonPay` hook provides a React interface for creating TON Pay transfers. It integrates with [TON Connect](/ecosystem/ton-connect/overview) to connect a wallet, sign transactions, and surface transfer errors through the hook state.

## How `useTonPay` works

The `useTonPay` hook manages the client-side flow for sending a TON Pay transfer. It performs the following steps:

1. Checks whether a wallet is connected and, if not, opens the TON Connect modal and waits for a successful connection.
2. Calls an application-provided factory function that builds the transaction message, either in the client-side or by requesting it from a backend service.
3. Sends the transaction message to the connected wallet for user approval and signing.
4. Returns the transaction result along with tracking identifiers that can be used for reconciliation.

## Integration approaches

`useTonPay` can be integrated in two ways, depending on where the transaction message is created.

### Client-side message building

* Message construction happens in the browser.
* All transaction fields are provided by the client.

### Server-side message building

* Message construction happens on the backend.
* Tracking identifiers are stored on the server before the message is returned to the client for signing.

## Client-side implementation

### Prerequisites

The application must be wrapped with the TON Connect UI provider:

```tsx theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
import { TonConnectUIProvider } from "@tonconnect/ui-react";

export function App() {
  return (
    <TonConnectUIProvider manifestUrl="https://<APP_URL>/tonconnect-manifest.json">
      {/* Application components */}
    </TonConnectUIProvider>
  );
}
```

[The TON Connect manifest](/ecosystem/ton-connect/manifest) file must be publicly accessible and include valid application metadata. Replace `<APP_URL>` with the public HTTPS origin that serves `tonconnect-manifest.json`.

### Basic implementation

```tsx expandable theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
import { useTonPay } from "@ton-pay/ui-react";
import { createTonPayTransfer } from "@ton-pay/api";

export function PaymentButton() {
  const { pay } = useTonPay();

  const handlePayment = async () => {
    try {
      const { txResult, message, reference, bodyBase64Hash } = await pay(
        async (senderAddr: string) => {
          const result = await createTonPayTransfer(
            {
              amount: 3.5,
              asset: "TON",
              recipientAddr: "<RECIPIENT_WALLET_ADDRESS>",
              senderAddr,
              commentToSender: "Payment for Order #8451",
            },
            {
              chain: "testnet",
              // Optional API key can be shown on the client-side, but the secret key never
              apiKey: "<TONPAY_API_KEY>", // optional
            }
          );

          return {
            message: result.message,
            reference: result.reference,
            bodyBase64Hash: result.bodyBase64Hash,
          };
        }
      );

      console.log("Transaction completed:", txResult.boc);
      console.log("Reference for tracking:", reference);
      console.log("Body hash:", bodyBase64Hash);

      // Store tracking identifiers in the database
      await savePaymentRecord({
        reference,
        bodyBase64Hash,
        amount: 3.5,
        asset: "TON",
      });
    } catch (error) {
      console.error("Payment failed:", error);
      // Handle error appropriately
    }
  };

  return <button onClick={handlePayment}>Pay 3.5 TON</button>;
}
```

### Response fields

The `pay` function returns the following fields:

<ResponseField name="txResult" type="SendTransactionResponse" required>
  Transaction result returned by TON Connect. It contains the signed transaction [bag of cells](/foundations/serialization/boc) and additional transaction details.
</ResponseField>

<ResponseField name="message" type="TonPayMessage" required>
  The transaction message that was sent, including the recipient address, amount, and payload.
</ResponseField>

<ResponseField name="reference" type="string">
  Unique tracking identifier for this transaction. Use it to correlate webhook notifications with orders.

  <Aside type="caution">
    Store the reference after creation. It is required to match incoming webhook notifications to specific orders.
  </Aside>
</ResponseField>

<ResponseField name="bodyBase64Hash" type="string">
  Base64-encoded hash of the transaction body. Can be used for advanced transaction verification.
</ResponseField>

## Server-side implementation

### Backend endpoint

Create an API endpoint that builds the transaction message and stores tracking data:

```typescript expandable theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
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: "<MERCHANT_WALLET_ADDRESS>",
        senderAddr,
        commentToSender: `Payment for Order ${orderId}`,
        commentToRecipient: `Order ${orderId}`,
      },
      {
        chain: "testnet",
        apiKey: "yourTonPayApiKey", // optional
      }
    );

    // 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" });
  }
});
```

<Aside type="caution" title="Persist tracking identifiers before responding">
  Always persist tracking identifiers such as `reference` and `bodyBase64Hash` in the database before returning the message to the client. If the client loses connection or closes the browser, these identifiers are still required to process incoming webhooks.
</Aside>

### Frontend implementation

```tsx expandable theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
import { useTonPay } from "@ton-pay/ui-react";

export function ServerPaymentButton({
  orderId,
  amount,
}: {
  orderId: string;
  amount: number;
}) {
  const { pay } = useTonPay();

  const handlePayment = async () => {
    try {
      const { txResult } = await pay(async (senderAddr: string) => {
        const response = await fetch("/api/create-payment", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ amount, senderAddr, orderId }),
        });

        if (!response.ok) {
          throw new Error("Failed to create payment");
        }

        const { message } = await response.json();
        return { message };
      });

      console.log("Transaction sent:", txResult.boc);

      // Optionally redirect or show success message
      window.location.href = `/orders/${orderId}/success`;
    } catch (error) {
      console.error("Payment failed:", error);
      alert("Payment failed. Please try again.");
    }
  };

  return <button onClick={handlePayment}>Pay {amount} TON</button>;
}
```

## Error handling

The `useTonPay` hook throws errors in the following scenarios:

### Wallet connection errors

```typescript theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
const { pay } = useTonPay();

try {
  await pay(getMessage);
} catch (error) {
  if (error.message === "Wallet connection modal closed") {
    // User closed the connection modal without connecting
    console.log("User cancelled wallet connection");
  } else if (error.message === "Wallet connection timeout") {
    // Connection attempt exceeded 5-minute timeout
    console.log("Connection timeout - please try again");
  }
}
```

### Transaction errors

```typescript theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
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);
  }
}
```

## Best practices

* Persist tracking identifiers immediately.

  ```typescript theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  // Good: Store before transaction
  const { message, reference } = await createTonPayTransfer(...);
  await db.createPayment({ reference, status: "pending" });
  return { message };

  // Bad: Only storing after successful transaction
  const { txResult, reference } = await pay(...);
  await db.createPayment({ reference }); // Too late if network fails
  ```

* Always validate or generate amounts server-side to prevent manipulation. Never trust amount values sent from the client.

  ```typescript theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  // Server-side endpoint
  app.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: '<RECIPIENT_WALLET_ADDRESS>',
          senderAddr,
      });

      res.json({ message });
  });
  ```

* Wrap the payment components with React error boundaries to handle failures.

  ```tsx theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  class PaymentErrorBoundary extends React.Component {
      componentDidCatch(error: Error) {
          console.error('Payment component error:', error);
          // Log to the error tracking service
      }

      render() {
          return this.props.children;
      }
  }
  ```

* Payment processing can take several seconds. Provide clear feedback to users during wallet connection and transaction signing.

  ```tsx theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  const [loading, setLoading] = useState(false);

  const handlePayment = async () => {
      setLoading(true);
      try {
          await pay(getMessage);
      } finally {
          setLoading(false);
      }
  };

  return (
      <button onClick={handlePayment} disabled={loading}>
          {loading ? 'Processing...' : 'Pay Now'}
      </button>
  );
  ```

* Use environment variables for sensitive data and chain configuration.

  ```typescript theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  const { message } = await createTonPayTransfer(params, {
    chain: process.env.TON_CHAIN as 'mainnet' | 'testnet',
    apiKey: process.env.TONPAY_API_KEY, // optional
  });
  ```

## Troubleshooting

<Accordion title="If the hook throws TonConnect provider not found">
  Wrap the application with `TonConnectUIProvider`:

  ```tsx theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  import { TonConnectUIProvider } from "@tonconnect/ui-react";

  function App() {
    return (
      <TonConnectUIProvider manifestUrl="https://<APP_URL>/tonconnect-manifest.json">
        <YourComponents />
      </TonConnectUIProvider>
    );
  }
  ```
</Accordion>

<Accordion title="If the wallet connection modal does not open">
  1. Verify that the TON Connect manifest URL is accessible and valid.
  2. Check the browser console for TON Connect initialization errors.
  3. Ensure the manifest file is served with correct [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) headers.
  4. Open the manifest URL directly in the browser to verify it loads.
</Accordion>

<Accordion title="If the transaction fails with Invalid recipient address">
  1. Verify that the recipient address is [a valid TON address](/foundations/addresses/formats).
  2. Ensure the address format matches the selected chain; mainnet or testnet.
  3. Verify that the address includes the full base64 representation with workchain.
</Accordion>

<Accordion title="If the factory function throws Failed to create TON Pay transfer">
  1. Check whether the optional API key is missing or invalid for endpoints that require authentication.
  2. Verify network connectivity.
  3. Validate parameter values. For example, non-negative amounts and valid addresses.
  4. Confirm the correct chain configuration; mainnet or testnet.

  To inspect the underlying API error:

  ```ts theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  try {
    await createTonPayTransfer(...);
  } catch (error) {
    console.error("API Error:", error.cause); // HTTP status text
  }
  ```
</Accordion>

<Accordion title="If the user rejects the transaction and no error is reported">
  1. Note that TON Connect may not emit an explicit error on rejection.
  2. Add timeout handling:

  ```ts theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  const paymentPromise = pay(getMessage);
  const timeoutPromise = new Promise((_, reject) =>
    setTimeout(() => reject(new Error("Transaction timeout")), 60000)
  );

  await Promise.race([paymentPromise, timeoutPromise]);
  ```
</Accordion>

## Optional API key configuration

When using `useTonPay` with server-side message building, the optional API key can be included in the backend endpoint to enable dashboard features and webhooks:

```typescript theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
// Backend endpoint
app.post("/api/create-payment", async (req, res) => {
  const { amount, senderAddr, orderId } = req.body;

  const { message, reference, bodyBase64Hash } = await createTonPayTransfer(
    {
      amount,
      asset: "TON",
      recipientAddr: process.env.MERCHANT_WALLET_ADDRESS!,
      senderAddr,
    },
    {
      chain: "testnet",
      apiKey: process.env.TONPAY_API_KEY, // Optional: enables dashboard features
    }
  );

  await db.createPayment({ orderId, reference, bodyBase64Hash });
  res.json({ message });
});
```

## Testnet configuration

<Aside type="danger" title="Funds at risk">
  Running tests on mainnet can result in irreversible loss of real TON. Always use `chain: "testnet"` and testnet wallet addresses during development. Verify the network before switching to mainnet.
</Aside>

A complete description of testnet configuration, including obtaining testnet TON, testing jetton transfers, verifying transactions, and preparing for mainnet deployment, is available in the [Testnet configuration](/ecosystem/ton-pay/payment-integration/transfer#testnet-configuration) section.

## Next steps

<CardGroup cols={2}>
  <Card title="Webhook integration" icon="webhook" href="/ecosystem/ton-pay/webhooks">
    Receive real-time notifications when payments complete.
  </Card>

  <Card title="Transaction status" icon="magnifying-glass" href="/ecosystem/ton-pay/payment-integration/status-info">
    Query payment status using reference or body hash.
  </Card>
</CardGroup>
