Đến nội dung
Tài liệu
Trên trang này

Hướng dẫn tích hợp

Tài liệu cho nhà phát triển

Tạo hóa đơn, chuyển khách hàng đến trang thanh toán, nhận webhook có chữ ký khi hóa đơn được thanh toán. Hãy bắt đầu với bốn bước dưới đây.

  1. Sao chép khóa API của dự án.
  2. Ký các yêu cầu ghi.
  3. Tạo hóa đơn đầu tiên.
  4. Xác minh webhook thanh toán.

Những gì được hỗ trợ

Cổng thanh toán chấp nhận USDT và USDC. Bảng này khớp với GET /v1/services, được kiểm tra vào ngày 4 tháng 11 năm 2026. Hãy đọc danh mục hiện hành trước khi xây dựng bộ chọn phương thức thanh toán.

Tài sảnMạngSố xác nhận
USDTTron20
USDTEthereum12
USDTSolana32
USDTPolygon8
USDTArbitrum12
USDTTON5
USDCEthereum12
USDCSolana32
USDCPolygon8
USDCBase12
USDCArbitrum12

Thanh toán theo hình thức lưu ký: tiền được chuyển đến các địa chỉ do nền tảng kiểm soát. Số dư người bán là số tiền chúng tôi nợ bạn. Ví Telegram là sản phẩm riêng và không lưu ký.

Giới hạn

  • Chi trả và hoàn tiền chưa hoạt động: GET /v1/services báo boundaries.livePayouts và boundaries.liveRefunds là false.
  • Các khoản thanh toán đến không được nhà cung cấp KYT sàng lọc.
  • Hóa đơn có phí vượt quá 20% số tiền sẽ bị từ chối với AMOUNT_BELOW_MINIMUM.
  • Không có SLA theo hợp đồng.

Mức phí khởi điểm là 0,40% cộng phí mạng. Hãy yêu cầu báo giá phí để biết tổng phí và số tiền tối thiểu; xem bảng giá.

Xác thực

Các lệnh gọi giữa máy chủ với máy chủ dùng x-api-key. Khóa dự án chỉ có hiệu lực trong một dự án người bán duy nhất. Hãy giữ khóa trên máy chủ của bạn.

Khóa thử nghiệm bắt đầu bằng ncp_test_…; khóa chính thức bắt đầu bằng ncp_live_…. Các khóa được cấp trước đây bắt đầu bằng ncp_sandbox_ và vẫn tiếp tục hoạt động.

curl -sS "https://api.veilnebu.la/v1/balances" \
  -H "x-api-key: $PROJECT_API_KEY"

Ký yêu cầu ghi

Yêu cầu GET chỉ cần x-api-key. Mọi yêu cầu POST, PUT, PATCH và DELETE được xác thực bằng khóa dự án còn cần thêm hai header, nếu không API sẽ trả về 401 Invalid API signature:

  • x-ncp-timestamp: ISO-8601, lệch không quá 5 phút so với hiện tại.
  • x-ncp-signature: hex(HMAC-SHA256(your API key, "<timestamp>.<METHOD>.<path incl. query>.<raw body>")).

Dùng tên phương thức viết hoa. Ký đúng các byte bạn gửi, bao gồm cả chuỗi truy vấn trong đường dẫn. Yêu cầu không có body thì ký chuỗi rỗng. Các công cụ hỗ trợ bên dưới cần khóa dự án của bạn; công cụ cho shell cần Bash, curl và OpenSSL, còn công cụ cho Node.js dùng fetch gốc.

export API_BASE_URL="https://api.veilnebu.la"
export PROJECT_API_KEY="ncp_test_…"

ncp() {
  local method="$1" path="$2" body="" ts sig
  shift 2
  if [ "$#" -gt 0 ]; then body="$1"; shift; fi
  ts=$(date -u +%Y-%m-%dT%H:%M:%S.000Z) || return
  sig=$(printf '%s.%s.%s.%s' "$ts" "$method" "$path" "$body" |
    openssl dgst -sha256 -hmac "$PROJECT_API_KEY" -hex) || return
  sig=${sig##* }
  if [ -n "$body" ]; then
    curl -sS --request "$method" "$API_BASE_URL$path" \
      -H "x-api-key: $PROJECT_API_KEY" \
      -H "x-ncp-timestamp: $ts" -H "x-ncp-signature: $sig" \
      -H "content-type: application/json" --data-binary "$body" "$@"
  else
    curl -sS --request "$method" "$API_BASE_URL$path" \
      -H "x-api-key: $PROJECT_API_KEY" \
      -H "x-ncp-timestamp: $ts" -H "x-ncp-signature: $sig" "$@"
  fi
}

Tạo hóa đơn đầu tiên

Tạo dự án trong bảng điều khiển, sao chép khóa API của dự án và tạo hóa đơn. Phản hồi chứa checkoutUrl: hãy chuyển khách hàng đến đó.

ncp POST /v1/payments '{
  "amountMinor":"10000",
  "currencyCode":"USD",
  "settlementAssetCode":"USDT",
  "externalId":"order-1001"
}' -H "idempotency-key: order-1001"

Trích đoạn phản hồi. ID được tạo riêng cho mỗi hóa đơn.

{
  "id": "cm3k9x2f40001qz8e7h5v1abc",
  "status": "new",
  "amountMinor": "10000",
  "currencyCode": "USD",
  "settlementAssetCode": "USDT",
  "checkoutUrl": "https://app.veilnebu.la/checkout/cm3k9x2f40001qz8e7h5v1abc"
}

amountMinor là chuỗi số nguyên tính theo đơn vị nhỏ nhất của loại tiền: 10000 = 100.00 USD. Trường thập phân amount chỉ được chấp nhận ở endpoint trình duyệt /v1/public/payments.

Thanh toán trên trình duyệt và các origin được phép

Tạo khóa công khai (publishable key) trong bảng điều khiển và liệt kê chính xác các origin mà cửa hàng của bạn sử dụng. Yêu cầu từ trình duyệt gửi khóa này dưới dạng x-publishable-key đến POST /v1/public/payments.

Origin, successUrl và cancelUrl đều phải nằm trong danh sách origin được phép của khóa. Origin gồm scheme, host và cổng (tùy chọn), không có đường dẫn hay ký tự đại diện. Origin bị thiếu hoặc không có trong danh sách sẽ bị từ chối.

Số tiền thập phân chấp nhận tối đa hai chữ số thập phân với tiền pháp định, và tối đa số chữ số thập phân của chính tài sản với tiền mã hóa (USDT và USDC: 6 trên mọi mạng chúng tôi hỗ trợ hiện nay).

const response = await fetch('https://api.veilnebu.la/v1/public/payments', {
  method: 'POST',
  headers: {
    'content-type': 'application/json',
    'x-publishable-key': 'pk_test_…',
  },
  body: JSON.stringify({
    amount: '100.00',
    currencyCode: 'USD',
    settlementAssetCode: 'USDT',
    successUrl: 'https://shop.example/thanks',
    cancelUrl: 'https://shop.example/cart',
  }),
});
if (!response.ok) throw new Error(await response.text());
const invoice = await response.json();
window.location.assign(invoice.checkoutUrl);

Trạng thái thanh toán

Trạng tháiViệc cần làm tiếp
newChia sẻ liên kết thanh toán
pendingĐang chờ thanh toán
confirmingĐang chờ xác nhận
paidSẵn sàng quyết toán
partially_paidĐề nghị khách hàng thanh toán nốt
overpaidKiểm tra số tiền thừa đã ghi có
expiredTạo hóa đơn mới
cancelledKhông cần làm gì
failedMở chi tiết
refundedKhông cần làm gì

Khóa idempotency

Khi tạo hóa đơn, hãy gửi một Idempotency-Key cố định được tạo từ ID đơn hàng của bạn. Thử lại với cùng khóa và cùng body sẽ trả về hóa đơn ban đầu. Dùng lại khóa đó với body khác sẽ trả về 409. Giữ nguyên khóa và body khi thử lại sau khi hết thời gian chờ.

Xem ví dụ hóa đơn đầu tiên để biết yêu cầu.

Lỗi

Lỗi được trả về dưới dạng JSON. message là một chuỗi, hoặc một mảng chuỗi đối với lỗi xác thực dữ liệu.

{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "Invalid API key"
}
Mã trạng thái HTTPÝ nghĩa và cách xử lý
400Trường không hợp lệ; message nêu tên trường. Hãy sửa trước khi thử lại.
401Invalid API key hoặc Invalid API signature. Kiểm tra khóa, các byte đã ký và dấu thời gian.
404Payment invoice not found. Kiểm tra ID và dự án.
409Cùng Idempotency-Key nhưng body khác, hoặc Insufficient merchant payable balance.
429Bị giới hạn tần suất. Hãy chờ trước khi thử lại.
5xxThử lại với cùng Idempotency-Key và body.

Webhook (HMAC-SHA256)

Mỗi lần gửi đều kèm x-nebula-signature: t=<unix>,v1=<hex>. Hãy xác minh HMAC bằng secret của endpoint được trả về khi bạn tạo endpoint. Từ chối dấu thời gian lệch quá 300 giây so với hiện tại.

Xác minh trong Node.js

Trình xác minh Node.js chỉ dùng mô-đun crypto có sẵn. Truyền vào header chữ ký, các byte body gốc và secret của endpoint.

import { createHmac, timingSafeEqual } from 'node:crypto';

export function verify(header, rawBody, secret) {
  const parts = {};
  for (const piece of (header ?? '').split(',')) {
    const separator = piece.indexOf('=');
    if (separator <= 0) continue;
    parts[piece.slice(0, separator).trim()] = piece.slice(separator + 1).trim();
  }
  if (!/^[0-9]{1,16}$/.test(parts.t ?? '')) return false;
  if (!/^[0-9a-f]{64}$/.test(parts.v1 ?? '')) return false;
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = createHmac('sha256', secret)
    .update(parts.t + '.')
    .update(rawBody)
    .digest('hex');
  if (expected.length !== parts.v1.length) return false;
  return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Xác minh trên body gốc của yêu cầu, trước khi phân tích JSON. Mỗi sự kiện được gửi ít nhất một lần: hãy loại trùng theo x-nebula-event-id, giá trị này khớp với eventId trong payload. Các lần thử lại tự động giữ nguyên ID lần gửi; gửi lại thủ công tạo ID lần gửi mới với cùng ID sự kiện. Hãy lưu sự kiện trước khi trả về 2xx.

Kiểm thử webhook trên máy cục bộ

URL webhook phải dùng một host công khai trên internet. Địa chỉ cục bộ và địa chỉ riêng sẽ bị từ chối. Hãy mở bộ nhận của bạn ra ngoài qua một đường hầm HTTPS công khai, rồi đăng ký URL đó bằng công cụ hỗ trợ ký.

ncp POST /v1/webhooks/endpoints '{
  "url":"https://receiver.example/webhook",
  "events":["payment.paid","payment.reorged"]
}'

Thay URL bằng bộ nhận của bạn. Lưu secret của endpoint được trả về khi tạo.

Loại sự kiện

Chi trả và hoàn tiền chưa hoạt động (GET /v1/services báo boundaries.livePayouts là false). Cho đến lúc đó, các lệnh chi trả và lô chi trả chỉ ghi nhận giao dịch chuyển trong số dư của bạn; payout.completed và refund.completed không có nghĩa là tiền đã được chuyển đi.

Danh mục dưới đây khớp với GET /v1/services. Một mục trong danh mục không có nghĩa là luồng chuyển tiền tương ứng đã hoạt động.

Sự kiệnÝ nghĩa
webhook.testMột lần gửi thử được yêu cầu cho một endpoint webhook.
payment.pendingKhách hàng đã chọn phương thức thanh toán; đang chờ khoản nạp đầu tiên.
payment.confirmingĐã phát hiện khoản nạp; đang chờ đủ số xác nhận cần thiết.
payment.paidHóa đơn đã được thanh toán đủ và quyết toán trên sổ cái.
payment.partially_paidNhận ít hơn số tiền yêu cầu; đang chờ phần còn lại.
payment.overpaidNhận nhiều hơn số tiền yêu cầu; đã quyết toán trên sổ cái cùng phần thanh toán thừa.
payment.expiredHóa đơn hết hạn mà không nhận đủ tiền.
payment.cancelledHóa đơn bị hủy trước khi thanh toán.
payment.failedThanh toán thất bại do lỗi chuỗi, chính sách phí hoặc lỗi theo dõi.
payment.risk_flaggedSự kiện rủi ro mang tính tham khảo cho một khoản nạp đã được ghi có. Sàng lọc KYT cho khoản đến chưa hoạt động.
payment.reorgedMột khoản nạp đã xác nhận bị loại bỏ do tái tổ chức chuỗi; hóa đơn không còn ở trạng thái đã thanh toán, vì vậy hãy tạm dừng hoặc hủy việc giao hàng.
payment.unsupported_assetĐã nạp một token không được hỗ trợ; khoản này không được ghi có.
payout.processingTrạng thái đang xử lý chi trả. Chuyển tiền thật chưa mở.
payout.completedLệnh chi trả đã được ghi nhận vào số dư; điều này không có nghĩa là tiền đã được chuyển đi.
payout.failedChi trả thất bại.
payout.cancelledLệnh chi trả bị hủy trước khi hoàn tất.
refund.processingTrạng thái đang xử lý hoàn tiền. Hoàn tiền thật chưa mở.
refund.completedHoàn tiền đã được ghi nhận vào số dư; điều này không có nghĩa là tiền đã được chuyển đi.
refund.failedHoàn tiền thất bại.
internal_transfer.sentGiao dịch chuyển số dư giữa người bán đã được thực hiện cho bên gửi.
internal_transfer.receivedGiao dịch chuyển số dư giữa người bán đã được ghi có cho bên nhận.
static_wallet.createdĐã tạo một địa chỉ nạp tiền dài hạn cho khách hàng.
static_wallet.blockedMột ví cố định đã bị chặn.
static_wallet.unblockedMột ví cố định đã được bỏ chặn.
static_wallet.topup.completedĐã phát hiện khoản nạp vào ví cố định và quyết toán trên sổ cái.
dispute.openedMột tranh chấp đã được mở đối với hóa đơn.
dispute.resolvedTranh chấp đã được giải quyết. Riêng việc giải quyết không chứng minh đã có hoàn tiền on-chain.
dispute.rejectedTranh chấp đã bị bác bỏ; người bán không cần làm gì.
subscription.invoice_createdMột chu kỳ thanh toán đã tạo hóa đơn mới.

Chính sách thử lại

Nếu endpoint của bạn không trả về 2xx, chúng tôi thử tổng cộng 8 lần: lần đầu, sau đó thử lại sau 1 phút, 5 phút, 30 phút, 2 giờ, 6 giờ, 12 giờ và 24 giờ. Mỗi khoảng chờ bắt đầu sau lần thử thất bại trước đó. Lỗi mạng và hết thời gian chờ cũng được tính là thất bại. Lần gửi thử chỉ có một lần thử.

Sau lần thất bại cuối cùng, lần gửi được đánh dấu là thất bại; hãy gửi lại bằng POST /v1/webhooks/deliveries/{deliveryId}/resend. Dùng GET /v1/payments/{id} để đối soát như phương án dự phòng.

Chi trả và hoàn tiền chưa hoạt động (GET /v1/services báo boundaries.livePayouts là false). Cho đến lúc đó, các lệnh chi trả và lô chi trả chỉ ghi nhận giao dịch chuyển trong số dư của bạn; payout.completed và refund.completed không có nghĩa là tiền đã được chuyển đi.

Lô chi trả

Một lô gom nhóm các bản ghi chuyển tiền. Chính sách phê duyệt và số lượt phê duyệt cần thiết của lô được trả về trong approvalPolicy và approvalsRequired. Khi có nhiều người phê duyệt, người tạo không thể phê duyệt lô của chính mình. Nếu chỉ có một nhân viên vận hành đủ điều kiện, người đó có thể phê duyệt lô do chính mình tạo trong phiên làm việc. Phê duyệt lô bằng WebAuthn trong bảng điều khiển.

export PROJECT_API_KEY="ncp_test_…"
ncp POST /v1/payout-batches '{
  "assetCode":"USDT",
  "networkCode":"TRON",
  "externalId":"batch-1001",
  "items":[{
    "destinationAddress":"T…",
    "amountMinor":"15000000",
    "externalRef":"recipient-1001"
  }]
}' -H "idempotency-key: batch-1001"

Thay địa chỉ nhận bằng một địa chỉ hợp lệ trong dự án thử nghiệm của chính bạn.

Thanh toán định kỳ

Gói đăng ký tạo một hóa đơn mới cho mỗi chu kỳ thanh toán và báo về bằng subscription.invoice_created. Gói đăng ký không tự động trừ tiền từ ví của khách hàng. Hãy gửi cho khách hàng URL trang thanh toán mới.

interval: DAY, WEEK, MONTH hoặc YEAR.

ncp POST /v1/subscriptions '{
  "customerRef":"customer-42",
  "assetCode":"USDT",
  "networkCode":"TRON",
  "amountMinor":"9000000",
  "interval":"MONTH",
  "intervalCount":1
}'

Tạm dừng, tiếp tục hoặc hủy bằng các thao tác vòng đời trong tài liệu tham chiếu API.

Sàng lọc rủi ro

Địa chỉ nhận của lệnh chi trả và hoàn tiền được đối chiếu với các danh sách trừng phạt công khai trước khi tạo giao dịch chuyển; nếu trùng khớp, yêu cầu sẽ bị từ chối. Các khoản thanh toán đến không được sàng lọc.

Chính sách AML và trừng phạt

Bot Telegram cho người bán

@veilnebula_bot làm việc với một dự án tại một thời điểm. Hãy liên kết bot trong cuộc trò chuyện riêng bằng khóa API của dự án đó. Bot sẽ cố xóa tin nhắn của bạn và mã hóa khóa được lưu. Nếu không xóa được, hãy thu hồi và thay khóa mới. Không bao giờ đăng khóa trong cuộc trò chuyện chung.

Thông báo chỉ áp dụng cho dự án đã liên kết và khóa đang hoạt động. Mở bảng điều khiển để phê duyệt chi trả.

/link <API_KEY>
/balance
/invoices
/notify on
/unlink
Hướng dẫn tích hợp · VeilNebula