Thanh toán crypto cho backend tự xây
Hướng dẫn không phụ thuộc công nghệ để tích hợp Qvard vào máy chủ của bạn: mô hình đơn hàng, tạo thanh toán, webhook, đối soát và danh sách kiểm tra trước khi chạy thật, kèm ví dụ Python.

Nếu cửa hàng, dịch vụ hay nền tảng của bạn chạy trên backend riêng, bạn không cần plugin: Qvard cần một lệnh gọi tạo thanh toán và một endpoint nhận kết quả. Bài này đi qua việc tích hợp theo cách phù hợp với mọi ngôn ngữ và framework, kèm ví dụ Python, và bao gồm những phần làm nó đáng tin cậy: mô hình đơn hàng, tính idempotent, đối soát và một danh sách kiểm tra ngắn trước khi chạy thật.
Bạn cần gì
- Một tài khoản Qvard có dự án, khóa API và callback URL của webhook đã lưu trong dự án.
- Một endpoint HTTPS công khai trên máy chủ của bạn cho webhook.
- Một bảng đơn hàng (hoặc tương đương) trong đó một khoản thanh toán có thể được ghép với một đơn bằng mã định danh của chính bạn.
Chưa có SDK và sandbox: việc tích hợp là HTTP thuần, và kiểm thử được thực hiện bằng số tiền thật nhỏ. Tài liệu tham khảo về định dạng yêu cầu nằm trong API thanh toán crypto cho nhà phát triển; bài này cho thấy chúng khớp với nhau thế nào.
Một mô hình đơn hàng tối thiểu
Dữ liệu của bạn quyết định việc tích hợp sạch đến đâu. Tập trường tối thiểu của một đơn hàng:
id: số đơn hàng hoặc hóa đơn của bạn. Đó làidbạn gửi cho Qvard và là khóa được trả lại trong webhook.amount_usd: số tiền bằng đô la Mỹ, đơn vị tạo thanh toán.state: ví dụawaiting_payment,paid,cancelled. Đánh dấu đã trả chỉ từ webhook.paid_atvà, nếu muốn,idthanh toán Qvard từ phản hồi để hỗ trợ.
Tránh nhồi toàn bộ logic thanh toán vào lớp đơn hàng. Một mô-đun nhỏ tạo thanh toán và một endpoint áp dụng webhook giúp mã dễ kiểm thử.
Tạo thanh toán
Khi khách chọn trả tiền, tạo thanh toán từ backend bằng một POST tới https://api.qvard.com/api/v1/payment. Gửi khóa trong header Authorization: Bearer và một JSON có số đơn hàng làm id (chuỗi), số tiền làm price bằng đô la Mỹ và tùy chọn paymentSystemId để mở trang thanh toán với tài sản và mạng đã chọn.
Phản hồi là 201 kèm redirectUrl, địa chỉ trang thanh toán được lưu trữ. Chuyển hướng khách tới đó, hoặc đặt liên kết vào email. Những quy tắc đáng đưa vào mã của client:
- Quá thời gian và thử lại. Dùng quá thời gian và thử lại khi quá thời gian hoặc
5xx. Gửi cùngidcho cùng phương thức thanh toán trả về khoản thanh toán hiện có, nên một lần thử lại không thể tạo khoản thứ hai. - Đừng thử lại
400(«Error validation», kèm thông báo theo từng trường) hay401(khóa sai) khi chưa thay đổi gì. - Số tiền bằng đô la. Nếu cửa hàng tính giá bằng tiền tệ khác, hãy quy đổi trước lệnh gọi.
import os
import requests
QVARD_URL = "https://api.qvard.com/api/v1/payment"
def create_payment(order_id: str, amount_usd: float) -> str:
"""Create a Qvard payment for an order and return the hosted payment page URL."""
response = requests.post(
QVARD_URL,
headers={"Authorization": "Bearer " + os.environ["QVARD_API_KEY"]},
json={"id": order_id, "price": amount_usd}, # id as a string, price in US dollars
timeout=15,
)
if response.status_code == 201:
return response.json()["redirectUrl"]
# 400 = "Error validation" with a per-field message, 401 = wrong API key
raise RuntimeError(f"Qvard {response.status_code}: {response.text}")Nhận webhook
Khi một thanh toán hoàn tất, Qvard gửi POST tới callback URL của bạn với id và status, có chữ ký trong header X-Qvard-Signature. Endpoint của bạn nên:
- Xác minh chữ ký: HMAC-SHA256 bằng khóa bí mật của bạn trên dấu thời gian và nội dung thô, so sánh theo thời gian không đổi; nếu không khớp thì trả
400. - Chỉ hành động khi trạng thái là
completed. - Cập nhật đơn có điều kiện, «đã trả nếu còn đang chờ thanh toán», để các lần lặp không làm gì.
- Xếp công việc chậm vào hàng đợi và trả lời đúng HTTP 200.
Qvard thử lại mọi câu trả lời khác, quá thời gian hay chuyển hướng, năm lần trong khoảng sáu phút, nên các lần gửi lặp là bình thường. Chi tiết thử lại nằm trong chiến lược thử lại webhook trong hệ thống thanh toán, còn quy tắc bảo mật trong cách bảo mật webhook thanh toán crypto.
import os
from fastapi import FastAPI, Request, Response
from qvard import Qvard
app = FastAPI()
qvard = Qvard(os.environ["QVARD_API_KEY"])
@app.post("/webhooks/qvard")
async def qvard_webhook(request: Request):
try:
body = qvard.webhooks.construct_event(await request.body(), request.headers.get("X-Qvard-Signature"), os.environ["QVARD_API_KEY"])
except ValueError:
return Response(status_code=401)
if body.get("status") == "completed":
# Only changes an order that is not paid yet, so repeated deliveries are harmless.
changed = await db.execute(
"UPDATE orders SET state = 'paid', paid_at = now() WHERE id = :id AND state = 'awaiting_payment'",
{"id": body.get("id")},
)
if changed:
await queue.enqueue("fulfil_order", body["id"])
return Response(status_code=200)Thanh toán một phần, lỗi và góc nhìn của khách
Hãy quyết định mỗi trạng thái thanh toán nghĩa là gì với một đơn:
wait: chờ thanh toán.partial: số tiền đến ít hơn đủ; webhook hoàn tất không được gửi. Giữ đơn mở, cho khách biết còn thiếu bao nhiêu, hoặc hủy và hoàn tiền.completed: đủ số tiền; đánh dấu đơn đã trả.rejected: đề nghị cách trả khác.
Chưa có URL quay lại sau khi thanh toán, nên khách tự quay lại: hãy cho họ trang đơn hàng hiển thị trạng thái thực và tự làm mới khi còn đang chờ. Hoàn tiền là khoản chuyển bạn tự gửi, vì thanh toán crypto không thể hoàn tác.
Đối soát và danh sách kiểm tra trước khi chạy thật
Thêm một bước kiểm tra hằng ngày so sánh các đơn bạn coi là đã trả hoặc chưa với các khoản thanh toán và số dư trong bảng điều khiển Qvard; một khoản thanh toán hiện hoàn tất trong khi đơn của bạn chưa trả là thông báo bị bỏ lỡ, bạn có thể gửi lại từ bảng điều khiển.
Trước khi chạy thật:
- khóa API nằm trong biến môi trường hoặc trình quản lý bí mật, không bao giờ trong kho mã;
- callback URL đã lưu, công khai, dùng HTTPS và truy cập được từ ngoài mạng của bạn;
- endpoint trả lời đúng 200, nhanh và an toàn khi chạy hai lần;
- bạn đã trả một khoản tiền thật nhỏ và thấy đơn chuyển sang đã trả;
- bạn đã cho endpoint lỗi một lần và thấy Qvard thử lại mà không thực hiện đơn hai lần;
- bạn đã rút một khoản nhỏ về ví của mình. Việc rút chỉ tốn phí mạng; rút về tài khoản ngân hàng bằng tiền pháp định không khả dụng.
Điều kiện thương mại nằm ở trang bảng giá, và một hóa đơn demo cho thấy khách của bạn sẽ thấy gì.
FAQ
Có SDK cho ngôn ngữ của tôi không?
Không. Việc tích hợp là HTTP thuần: một POST để tạo thanh toán và một webhook để nhận kết quả. Các ví dụ trong bài cho thấy mẫu này bằng Python và có thể chuyển sang mọi ngôn ngữ.
Làm sao ghép một khoản thanh toán với đơn hàng?
Gửi số đơn hàng của chính bạn làm id khi tạo thanh toán. Cùng id đó được trả lại trong webhook.
Có sandbox để thử không?
Chưa có. Hãy dùng số tiền thật nhỏ và một hóa đơn demo để xem trang thanh toán.
Nếu máy chủ của tôi ngừng khi thanh toán hoàn tất thì sao?
Qvard thử lại năm lần trong khoảng sáu phút. Sau đó hãy gửi lại thông báo của khoản thanh toán từ bảng điều khiển; trình xử lý idempotent làm cho việc này an toàn.
