Cách nhận thanh toán crypto trên website
Từng bước: tạo thanh toán từ máy chủ, chuyển khách đến trang thanh toán, xử lý webhook và kiểm thử. Kèm mã Node.js chạy được.

Để nhận crypto trên website, bạn cần hai thứ trên máy chủ: một lệnh gọi tạo thanh toán cho đơn hàng và một điểm cuối nhận kết quả. Hướng dẫn này đi qua cả hai bằng mã chạy được, rồi nói về kiểm thử, các trường hợp đặc biệt và những lỗi khiến merchant mất tiền.
Bạn cần gì trước khi bắt đầu
Bạn không cần chạy node blockchain hay giữ ví của khách. Bạn cần:
- một tài khoản cổng thanh toán có dự án, khóa API và, nếu muốn tự cố định phương thức thanh toán, ID của phương thức đó trong dự án;
- một điểm cuối HTTPS công khai trên máy chủ cho webhook, được lưu trong dự án làm callback URL;
- một chỗ trong hệ thống đơn hàng để lưu trạng thái thanh toán, đối chiếu theo số đơn hàng của chính bạn.
Hãy quyết định sớm một việc: khách tự chọn tài sản và mạng trên trang thanh toán, hay bạn chọn sẵn. Để khách chọn thì đơn giản hơn và phù hợp với đa số cửa hàng. Nếu bạn truyền phương thức thanh toán, trang sẽ mở với phương thức đó đã được chọn.
Toàn bộ quy trình khớp với nhau như thế nào
Đây là đường đi của một đơn hàng, từ bước thanh toán của bạn đến trạng thái đã trả:
- Khách chọn «Thanh toán bằng crypto» ở bước thanh toán và máy chủ của bạn tạo thanh toán cho đơn đó.
- Cổng trả về đường dẫn đến trang thanh toán được lưu trữ.
- Bạn chuyển hướng khách đến trang đó. Khách chọn tài sản và mạng rồi gửi đúng số tiền từ ví của mình.
- Cổng theo dõi blockchain và, khi thanh toán đã chốt, gọi webhook của bạn.
- Webhook của bạn đánh dấu đơn là đã thanh toán và bắt đầu xử lý đơn.
Hãy chú ý điều không bao giờ xảy ra ở phía bạn: bạn không tạo địa chỉ, không quét khối và không so sánh số tiền. Mã của bạn làm hai việc nhỏ: tạo thanh toán và phản ứng với trạng thái cuối.
Bước 1: tạo thanh toán từ máy chủ của bạn
Hãy gọi cổng từ backend, không bao giờ từ trình duyệt, vì yêu cầu mang khóa API bí mật của bạn. Với Qvard đó là một POST đến https://api.qvard.com/api/v1/payment với khóa trong header x-api-key và phần thân JSON:
id: số đơn hàng của chính bạn, dạng chuỗi. Nó được trả lại cho bạn sau này trong webhook, nên hãy dùng giá trị mà bạn có thể tra cứu.price: số tiền bằng đô la Mỹ. Khách trả giá trị tương đương bằng tài sản họ chọn.paymentSystemId(tùy chọn): ID của một phương thức thanh toán trong dự án, để chọn sẵn tài sản và mạng.
Khi thành công, phản hồi là 201 kèm id thanh toán và redirectUrl. Gửi lại cùng id với cùng phương thức thanh toán sẽ trả về khoản thanh toán đã có thay vì tạo bản trùng, nên thử lại sau khi hết thời gian chờ là an toàn.
Nếu yêu cầu sai, bạn nhận 400 với nội dung Error validation và một thông báo cho mỗi trường; số tiền thấp hơn mức tối thiểu của phương thức đã chọn cũng bị từ chối như vậy. Thiếu hoặc sai khóa trả về 401.
// Called from your checkout handler when the customer chooses "Pay with crypto".
async function createQvardPayment(order) {
const res = await fetch('https://api.qvard.com/api/v1/payment', {
method: 'POST',
headers: {
'x-api-key': process.env.QVARD_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: String(order.id), // your order number, also used to match the webhook
price: order.totalUsd // amount in US dollars
})
});
if (!res.ok) throw new Error('Qvard ' + res.status + ' ' + (await res.text()));
const { redirectUrl } = await res.json();
return redirectUrl; // send the customer to this page
}Bước 2: chuyển khách đến trang thanh toán
Chuyển hướng khách đến redirectUrl, có dạng https://my.qvard.com/pay/<ID thanh toán>. Trên trang đó khách chọn tài sản và mạng, thấy số tiền chính xác cùng địa chỉ và thanh toán từ ví. Tỷ giá được cố định một giờ, nên số tiền không đổi khi khách đang trả.
Hai điều cần lên kế hoạch:
- Khi khách quay lại. URL quay về sau thanh toán chưa có, nên khách tự quay lại trang của bạn. Hãy cho họ một trang đơn hàng hiển thị trạng thái hiện tại và cập nhật khi webhook của bạn đến.
- Đừng coi việc quay về là đã thanh toán. Khách đã quay lại trang của bạn chưa chắc đã trả tiền. Chỉ webhook cho bạn biết tiền đã về.
Bước 3: nhận webhook
Khi một thanh toán hoàn tất, Qvard gửi một POST đến callback URL của dự án với một JSON nhỏ: id bạn đã gửi và status của thanh toán. Yêu cầu mang khóa API của bạn trong header x-api-key. Đây là header xác thực, không phải chữ ký mật mã: hãy so sánh nó với khóa của bạn và từ chối mọi thứ khác.
Cách webhook được gửi đi rất quan trọng với trình xử lý của bạn:
- Qvard chỉ coi webhook là đã gửi thành công khi điểm cuối của bạn trả lời đúng HTTP 200. Mọi trạng thái khác hoặc hết thời gian chờ đều tính là thất bại.
- Lần gửi thất bại sẽ được thử lại: tổng cộng năm lần, trong khoảng sáu phút.
- Qvard không đi theo chuyển hướng (redirect), nên phản hồi
3xxcũng là thất bại. - Bạn cũng có thể gửi lại thông báo của một thanh toán từ trang quản trị.
Vì có thử lại, trình xử lý của bạn phải idempotent: thấy cùng một sự kiện hai lần không được giao đơn hai lần. Ví dụ bên dưới kiểm tra khóa, tìm đơn theo id của bạn và chỉ đánh dấu đã thanh toán nếu đơn chưa được thanh toán.
import crypto from 'node:crypto';
import express from 'express';
const app = express();
app.post('/webhooks/qvard', express.json(), async (req, res) => {
// Qvard sends your API key in the x-api-key header: compare it with yours.
const received = Buffer.from(String(req.get('x-api-key') || ''));
const expected = Buffer.from(process.env.QVARD_API_KEY);
const valid = received.length === expected.length && crypto.timingSafeEqual(received, expected);
if (!valid) return res.sendStatus(401);
const { id, status } = req.body; // id is the order number you sent
const order = await db.orders.findById(id);
if (!order) return res.sendStatus(200); // unknown order: nothing to retry
if (status === 'completed' && order.state !== 'paid') {
await markOrderPaid(order); // safe to run twice: the check above skips repeats
}
return res.sendStatus(200); // must be exactly 200, otherwise Qvard retries
});Xử lý các thanh toán không suôn sẻ
Một thanh toán có một trong bốn trạng thái:
wait: đã tạo, đang chờ khách.partial: khách gửi ít hơn mức cần.completed: đã nhận đủ số tiền; đây là trạng thái kích hoạt webhook.rejected: thanh toán bị từ chối.
Hãy quyết định trước cửa hàng sẽ làm gì với từng trạng thái. Với partial, lựa chọn thông thường là giữ đơn mở và yêu cầu khách gửi phần còn lại, hoặc hoàn tiền. Giữ logic đơn giản: chỉ đánh dấu đơn đã thanh toán khi completed và hiển thị thông báo rõ ràng cho khách ở mọi trạng thái khác.
Kiểm thử trước khi chạy thật
Một kế hoạch kiểm thử ngắn bắt được hầu hết các vấn đề:
- Mở một hóa đơn demo để xem trang thanh toán như khách của bạn sẽ thấy.
- Tạo thanh toán từ máy chủ và kiểm tra bạn nhận được
redirectUrl. - Thanh toán một khoản thật nhỏ và xác nhận webhook đến và đơn chuyển sang đã thanh toán.
- Cho điểm cuối của bạn trả lỗi một lần, xác nhận Qvard thử lại và trình xử lý không xử lý đơn hai lần.
- Rút một khoản nhỏ từ số dư về ví để biết toàn bộ đường đi hoạt động.
Qvard chưa có sandbox riêng; một sandbox nằm trong lộ trình, vì vậy hãy dùng các khoản thật nhỏ cho bước 3 đến 5.
Danh sách kiểm tra bảo mật
- Chỉ giữ khóa API trên máy chủ. Không bao giờ đặt nó trong mã front-end hay kho mã công khai.
- So sánh header
x-api-keyở mỗi webhook và từ chối khi không khớp, dùng phép so sánh thời gian không đổi. - Phục vụ điểm cuối qua HTTPS và giữ cho nó nhanh; việc chậm hãy làm sau khi đã trả lời.
- Đối chiếu thanh toán với đơn theo
idcủa chính bạn và bỏ qua sự kiện của đơn bạn không biết. - Ghi log mỗi webhook kèm trạng thái để sau này giải quyết tranh chấp.
Xem thêm ở cách bảo vệ webhook thanh toán crypto.
Còn WooCommerce, Shopify hay nền tảng khác thì sao
Áp dụng đúng hai lệnh gọi đó: mã của nền tảng bạn tạo thanh toán và nhận webhook. Qvard chưa có plugin dựng sẵn, nên trên nền tảng như WooCommerce, việc tích hợp là một đoạn mã tùy chỉnh nhỏ hoặc một giờ làm việc của lập trình viên, không phải kiểu cài đặt chỉ cần một cú nhấp. Xem tích hợp để biết hiện trạng, và trang nhà phát triển để xem tài liệu API. Nếu bạn muốn bức tranh chung trước, hãy bắt đầu với cổng thanh toán crypto là gì.
FAQ
Tôi có cần biết lập trình blockchain không?
Không. Bạn gửi một yêu cầu HTTP để tạo thanh toán và xử lý một webhook. Địa chỉ, giám sát mạng và xác nhận do cổng đảm nhận.
Tôi có thể nhận crypto mà không viết mã không?
Chưa được với Qvard. Liên kết thanh toán và các cách không cần mã khác đang được phát triển; hiện nay tích hợp qua API, cần một lập trình viên.
Nếu máy chủ của tôi ngừng hoạt động khi thanh toán hoàn tất thì sao?
Thông báo được thử lại năm lần trong khoảng sáu phút. Nếu máy chủ ngừng lâu hơn, hãy gửi lại thông báo của thanh toán đó từ trang quản trị và đối chiếu từ đó.
Tôi đặt giá bằng đơn vị tiền tệ nào?
Bằng đô la Mỹ. Khách trả giá trị tương đương bằng tài sản và mạng họ chọn trên trang thanh toán, theo tỷ giá được cố định một giờ.
