Cách nhận thanh toán USDT (TRC-20) trên website của bạn
Vì sao TRC-20 trở thành mạng mặc định của USDT
USDT không phải một token duy nhất. Đó là cùng một thương hiệu được phát hành riêng trên Tron (TRC-20), Ethereum (ERC-20), BNB Smart Chain (BEP-20) và vài mạng khác. Trong ví của khách hàng, tất cả đều hiện là "USDT" — và đó chính là nơi phần lớn giao dịch hỏng bắt đầu.
Với số tiền bán lẻ hằng ngày, Tron thắng nhờ chi phí. Một giao dịch TRC-20 trả phí bằng TRX (hoặc bằng bandwidth/energy mà người gửi đã stake), và đó là một khoản gần như cố định chứ không phải phần trăm của số tiền. Phí ERC-20 trên Ethereum lại phụ thuộc mức tắc nghẽn mạng: vào ngày cao điểm, phí gas cho một hoá đơn 25 USD có thể chiếm một tỉ lệ khó chấp nhận. Thời gian xác nhận cũng khác: block Tron sinh ra sau vài giây, Ethereum khoảng mười hai giây, Bitcoin trung bình mười phút.
Kết luận thực dụng cho merchant rất ngắn. Nếu chỉ hỗ trợ một mạng, hãy chọn TRC-20. Nếu hỗ trợ hai, thêm ERC-20 cho những khách hàng vốn đã giữ số dư trên Ethereum và không muốn bắc cầu qua mạng khác.
Ba cách nhận USDT và cái giá của từng cách
| Cách làm | Ai giữ tiền | Chi phí | Điểm gãy chính |
|---|---|---|---|
| Dán một địa chỉ ví lên trang thanh toán | Bạn | Không mất phí dịch vụ | Không biết khoản tiền về thuộc đơn nào. Đối soát thủ công và càng tăng đơn càng tệ |
| Cổng custodial hoặc trang thanh toán của sàn | Nhà cung cấp, cho tới khi bạn rút | Phí phần trăm, cộng phí rút | Doanh thu nằm trong tài khoản của người khác. Tài khoản bị rà soát, hạn mức rút hay nhà cung cấp phá sản đều thành vấn đề của bạn |
| Cổng non-custodial | Bạn, mọi lúc | Phí phần trăm | Bạn tự chịu trách nhiệm bảo mật ví của mình. Không có ai đảo ngược một khoản chuyển nhầm |
Cách thứ nhất là nơi gần như ai cũng bắt đầu, và nó chạy được cho tới ngày có hai khách hàng trả cùng một số tiền. Cách thứ hai dễ tích hợp nhất và khó gỡ ra nhất về sau. Cách thứ ba là chủ đề của bài này, vì đó là cách duy nhất mà khoản thanh toán không bao giờ trở thành nghĩa vụ nợ của một bên khác. Nếu muốn so sánh kỹ hơn, chúng tôi có một bài riêng về cổng custodial và non-custodial.
Cần chuẩn bị gì trước khi viết dòng code nào
NextCryptoPay là non-custodial, nên không bao giờ hỏi bạn khoá riêng. Thứ nó cần là một đích nhận công khai mà bạn vốn đã kiểm soát:
- Một khoá công khai mở rộng (xpub) — khuyến nghị. Hệ thống derive một địa chỉ nhận mới cho từng hoá đơn từ khoá công khai đó, và không thể ký bất cứ thứ gì bằng nó. Cơ chế này được mô tả ở trang bảo mật.
- Hoặc một địa chỉ tĩnh duy nhất — có hỗ trợ, kèm một hệ quả quan trọng nói ở dưới.
Bạn cũng cần một API key trong dashboard, dạng ncp_live_ cộng 48 ký tự hex. Nó chỉ hiện đúng một lần. Hãy lưu vào biến môi trường phía máy chủ; tuyệt đối không để lọt xuống trình duyệt.
Các cặp chain/asset đang hỗ trợ:
chain | giá trị asset hợp lệ | Số xác nhận mặc định |
|---|---|---|
tron | USDT, USDC | 20 |
ethereum | USDT, USDC | 12 |
bsc | USDT | 15 |
bitcoin | BTC | 3 |
ton | USDT | 1 |
Mọi tổ hợp khác bị từ chối với 400 CHAIN_ASSET_INVALID.
Tạo hoá đơn
Một lời gọi phía máy chủ là đủ để tạo hoá đơn và nhận về trang thanh toán dựng sẵn:
curl -X POST https://nextcryptopay.com/api/v1/invoices \
-H "Authorization: Bearer ncp_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1001-attempt-1" \
-d '{
"orderId": "ORDER-1001",
"amount": 99.50,
"currency": "USD",
"chain": "tron",
"asset": "USDT",
"webhookUrl": "https://yourstore.com/webhooks/nextcryptopay",
"redirectUrl": "https://yourstore.com/thanks?order=ORDER-1001"
}'
{
"id": "inv_abc123",
"orderId": "ORDER-1001",
"payAddress": "TJ9xH4n2k8sQw7Lm3vR5pZ1aB6cD8eF2g",
"cryptoAmount": "99.50",
"asset": "USDT",
"chain": "tron",
"status": "PENDING",
"expiresAt": "2026-09-16T10:15:00.000Z",
"checkoutUrl": "https://nextcryptopay.com/pay/inv_abc123"
}
Ba chi tiết rất dễ làm sai:
cryptoAmountlà chuỗi. Hãy coi nó là chuỗi thập phân ở mọi nơi — trong database, trong template, khi so sánh. Parse thành float rồi format lại chính là cách bạn hiển thị một con số mà khách hàng không thể trả đúng.orderIdlà duy nhất trong mỗi shop. Gửi trùngorderIdsẽ trả về hoá đơn đã có với HTTP 200 thay vì tạo hoá đơn thứ hai. Đây là hành vi cố ý: một nút thanh toán bị bấm hai lần không được đẻ ra hai hoá đơn.- Gửi
Idempotency-Keykhi retry. Nếu request của bạn timeout rồi thử lại với cùng key, bạn nhận đúng phản hồi đã lưu thay vì một hoá đơn mới.
Hoá đơn hết hạn sau mười lăm phút kể từ lúc tạo (mặc định). Hãy lưu id, orderId và status trước khi chuyển khách sang checkoutUrl.
xpub hay địa chỉ tĩnh: khác nhau ở khâu khớp đơn
Với ví xpub, mỗi hoá đơn có địa chỉ riêng. Địa chỉ chính là mã định danh, nên trả dư vài xu vẫn khớp được hoá đơn, và khoản trả muộn vào một địa chỉ đã hết hạn vẫn đối soát được.
Với địa chỉ tĩnh, mọi hoá đơn dùng chung một địa chỉ, nên thứ duy nhất phân biệt chúng là số tiền chính xác. Hệ thống vì thế gán cho mỗi hoá đơn đang mở một số tiền duy nhất. Hệ quả rất thật:
- Khách hàng phải gửi đúng
cryptoAmount. Hiển thị nguyên văn, không bao giờ làm tròn. - Trả thiếu hoặc trả dư đều không làm hoá đơn chuyển sang đã thanh toán. Khoản đó được ghi nhận để đối soát thủ công, còn hoá đơn vẫn hết hạn bình thường.
- Nếu quá nhiều hoá đơn mở cùng lúc, có thể không còn số tiền duy nhất nào trống và việc tạo hoá đơn trả về
503 NO_AMOUNT_SLOT. Hãy thử lại sau khi một số hoá đơn được thanh toán hoặc hết hạn.
Có một thói quen của khách hàng đáng cảnh báo ngay trên giao diện của bạn, bất kể dùng loại ví nào: trả tiền trực tiếp từ tài khoản sàn. Sàn trừ phí rút vào chính số tiền gửi đi, nên số về luôn ít hơn số được yêu cầu.
Xác nhận, và lúc nào thì an toàn để giao hàng
Một khoản thanh toán nhìn thấy trên chain chưa phải là khoản thanh toán chung cuộc. Hoá đơn chuyển sang CONFIRMING ngay khi giao dịch xuất hiện, và chỉ sang PAID khi đạt ngưỡng xác nhận của chain tương ứng trong bảng ở trên. Hãy giao hàng ở PAID, không bao giờ ở CONFIRMING, và cũng không bao giờ dựa vào việc khách quay về redirectUrl — một lần chuyển hướng chỉ chứng minh rằng một trình duyệt đã đi theo một đường link.
Danh sách trạng thái đầy đủ: NEW, PENDING, CONFIRMING, PARTIALLY_PAID, PAID, EXPIRED, FAILED, CANCELLED. Bốn trạng thái cuối là chung cuộc.
Xử lý webhook
Mỗi khi trạng thái đổi, hệ thống POST một sự kiện có chữ ký tới webhook URL của bạn: invoice.confirming, invoice.paid, invoice.expired, cộng thêm invoice.updated khi bạn gửi lại sự kiện thủ công từ dashboard.
Mỗi lần giao đều mang chữ ký HMAC-SHA256 trên chuỗi {timestamp}.{rawBody} ở header x-nextcryptopay-signature, một delivery id ổn định ở x-nextcryptopay-delivery, và thời điểm ký ở x-nextcryptopay-timestamp. Bộ header cũ x-cryptopay-* mang giá trị y hệt và vẫn được gửi song song tới 31/03/2027.
Việc xác minh không phải tuỳ chọn. Một endpoint webhook không xác minh chữ ký là một endpoint giao hàng miễn phí cho bất kỳ ai đoán được URL. Cơ chế, năm lỗi kinh điển và mã nguồn đầy đủ nằm trong bài riêng về xác minh webhook.
Nếu endpoint của bạn không trả 2xx trong mười lăm giây, hệ thống thử lại tối đa sáu lần với backoff 1, 5, 30, 120, 360 và 1440 phút. Redirect không được đi theo, và URL trỏ về dải mạng nội bộ bị từ chối thẳng.
Những sai lầm tốn tiền thật
- Gửi TRC-20 vào địa chỉ ERC-20 hoặc ngược lại. Hãy hiện tên mạng ngay cạnh địa chỉ ở mọi chỗ bạn render nó, không chỉ ở trang thanh toán.
- Giao hàng dựa vào redirect. Redirect chỉ là tiện lợi giao diện. Webhook và API mới là nguồn sự thật.
- Xử lý đơn không idempotent. Webhook có thể được retry và gửi lại thủ công. Giao hàng hai lần là lỗi rất thật và rất phổ biến.
- Làm tròn số tiền. Với ví địa chỉ tĩnh, việc này âm thầm phá khâu khớp đơn.
- Poll endpoint hoá đơn trong vòng lặp dày. Hãy dùng luồng Server-Sent Events ở
/api/v1/invoices/{id}/stream, hoặc chỉ cần dùng webhook. Việc tạo hoá đơn bị giới hạn 60 lần mỗi phút cho mỗi shop.
Đi tiếp từ đâu
Tài liệu API đầy đủ nằm ở trang tài liệu, kèm một bản máy đọc được tại /docs-agent.md mà bạn có thể đưa thẳng cho AI coding agent. Biểu phí ở trang bảng giá, còn những câu merchant hay hỏi nhất được gom ở trang FAQ.