DM Champ Docs

Tự động nạp tiền cho tài khoản phụ (Nhà cung cấp thanh toán tùy chỉnh)

Nếu bạn muốn xử lý các khoản thanh toán tín dụng cho tài khoản phụ thông qua nhà cung cấp của riêng mình thay vì Stripe (chuyển khoản ngân hàng, cổng thanh toán nội địa, hệ thống thanh toán tùy chỉnh), nền tảng cung cấp một cặp webhook + API để bạn có thể tự thực hiện toàn bộ quy trình tính phí và cấp tín dụng. Tổng quan phi kỹ thuật nằm trên trang Tài khoản Đại lý; trang này bao gồm nội dung webhook chính xác và lệnh gọi API bạn thực hiện để cấp tín dụng sau đó.

Webhook chỉ là một trong những cách để thanh toán cho việc nạp tiền tự động. Nếu bạn đã kết nối Stripe hoặc PayPal ở Chế độ SaaS, nền tảng sẽ tự động tính phí vào thẻ hoặc tài khoản PayPal đã lưu của khách hàng và cấp tín dụng, vì vậy bạn không cần phải xây dựng bất cứ thứ gì trên trang này. Chỉ cần đọc tiếp nếu bạn muốn tự mình xử lý việc thanh toán.


Sơ lược về quy trình

  1. Số dư tín dụng của tài khoản phụ giảm xuống dưới ngưỡng tự động nạp tiền.
  2. Nền tảng gọi URL webhook của bạn với thông tin chi tiết về tài khoản phụ và số lượng tín dụng họ cần.
  3. Máy chủ của bạn tính phí khách hàng thông qua bất kỳ nhà cung cấp nào bạn sử dụng.
  4. Máy chủ của bạn gọi API Cấp tín dụng để thêm tín dụng vào tài khoản phụ đó.
  5. Máy chủ của bạn phản hồi 200 để xác nhận webhook.

1. Webhook: agency_sub_account_auto_recharge

Cấu hình URL webhook bằng cách nhấp vào SaaS Mode trong thanh bên chính, chọn Use a Custom Payment Provider Instead (hoặc mở thẻ Custom Payment Provider sau khi đã cấu hình), và điền vào trường Auto-Recharge Webhook.

Khi nào sự kiện được kích hoạt

Khi số dư tín dụng của tài khoản phụ giảm xuống dưới ngưỡng tự động nạp tiền đã cấu hình.

Payload

{
  "event": "agency_sub_account_auto_recharge",
  "sub_account_id": "<sub-account-id>",
  "sub_account_email": "customer@example.com",
  "sub_account_name": "John Doe",
  "agency_id": "<agency-id>",
  "credits_requested": 500,
  "current_balance": 42,
  "threshold": 100,
  "price_per_credit_cents": 10,
  "price_per_credit_currency": "usd",
  "total_amount_cents": 5000,
  "timestamp": "2026-04-13T12:00:00.000Z",
  "idempotency_key": "auto_recharge_abc123_1681387200000"
}
Trường Mô tả
event Luôn là agency_sub_account_auto_recharge cho webhook này.
sub_account_id ID duy nhất của tài khoản phụ cần tín dụng.
sub_account_email Địa chỉ email của tài khoản phụ.
sub_account_name Tên hiển thị của tài khoản phụ.
agency_id ID duy nhất của tài khoản đại lý của bạn.
credits_requested Số lượng tín dụng cần cấp. Đây là số tiền nạp đã cấu hình của bạn, nhưng nếu số dư đã giảm xuống dưới ngưỡng nhiều hơn số tiền đó, nó sẽ tự động được tăng lên mức cần thiết để đưa số dư trở lại trên ngưỡng trong một lần. Luôn tính phí (và cấp) giá trị credits_requested từ payload — đừng mã hóa cứng số tiền nạp của bạn.
current_balance Số dư tín dụng của tài khoản phụ tại thời điểm webhook được gửi.
threshold Ngưỡng số dư đã kích hoạt việc nạp tiền.
price_per_credit_cents Giá mỗi tín dụng đã cấu hình của bạn, tính bằng cent.
price_per_credit_currency Đơn vị tiền tệ cho giá (ví dụ: usd).
total_amount_cents Tổng số tiền cần tính phí, tính bằng cent (credits_requested × price_per_credit_cents).
timestamp Thời điểm webhook được gửi (định dạng ISO 8601).
idempotency_key Một khóa duy nhất cho yêu cầu nạp tiền cụ thể này. Sử dụng khóa này để ngăn chặn việc cấp tín dụng hai lần nếu máy chủ của bạn nhận được cùng một webhook nhiều lần.

Lưu ý quan trọng

  • Thời gian chờ 5 phút — Sau một lần thử nạp tiền cho tài khoản phụ, nền tảng sẽ không gửi webhook khác cho tài khoản phụ đó trong ít nhất 5 phút, ngay cả khi số dư của họ giảm thêm. Điều này ngăn chặn các khoản phí trùng lặp trong quá trình xử lý.
  • Sử dụng khóa idempotency — Luôn kiểm tra idempotency_key trước khi cấp tín dụng. Nếu máy chủ của bạn bị lỗi sau khi cấp tín dụng nhưng trước khi phản hồi, nền tảng có thể gửi lại webhook vào lần giảm tín dụng tiếp theo.
  • Lỗi là an toàn và tự phục hồi — Nếu URL webhook của bạn không thể truy cập hoặc trả về lỗi, sẽ không có tín dụng nào được cấp. Nền tảng tiếp tục gửi lại webhook (mỗi lần chờ 5 phút) miễn là tài khoản phụ vẫn ở dưới ngưỡng — nó không yêu cầu số dư phải tăng trở lại trên ngưỡng rồi mới giảm xuống. Một khoản phí bị bỏ lỡ sẽ không còn khiến tài khoản phụ bị ngừng nạp tiền vĩnh viễn.
  • Đặt số tiền nạp bằng hoặc trên ngưỡng — Số tiền nạp đã cấu hình của bạn phải lớn hơn hoặc bằng ngưỡng tự động nạp tiền, để một lần nạp luôn khôi phục số dư trên ngưỡng. (Nếu bạn cần sửa một tài khoản phụ đã giảm xuống quá xa dưới ngưỡng, nền tảng sẽ tự động nạp đầy phần thiếu hụt — xem credits_requested ở trên.)

2. API Cấp tín dụng

Sau khi máy chủ của bạn đã xử lý thanh toán, hãy gọi endpoint này để thêm tín dụng vào tài khoản phụ.

Yêu cầu

POST https://api.dmchamp.com/v1/subaccounts/credits
X-API-Key: YOUR_API_KEY
Content-Type: application/json

{
  "email": "customer@example.com",
  "amount": 500,
  "description": "Auto-recharge via webhook"
}
Trường Bắt buộc Mô tả
email Địa chỉ email của tài khoản phụ (phải khớp với tài khoản phụ hiện có dưới đại lý của bạn).
amount Số lượng tín dụng cần cấp.
description Không Ghi chú mô tả lý do tại sao tín dụng được thêm (hiển thị trong lịch sử giao dịch tín dụng).

Xác thực: Gửi khóa API của bạn trong tiêu đề yêu cầu — sử dụng X-API-Key: YOUR_API_KEY hoặc Authorization: Bearer YOUR_API_KEY. Sử dụng tiêu đề là cách được khuyến nghị, vì khóa trong URL có thể bị lưu lại trong lịch sử trình duyệt, nhật ký proxy và nhật ký truy cập máy chủ. Tham số truy vấn ?apiKey=YOUR_API_KEY và trường apiKey trong phần thân JSON vẫn hoạt động, vì vậy các tích hợp cũ hơn vẫn tiếp tục chạy mà không cần thay đổi.

Phản hồi

{
  "success": true,
  "sub_account_id": "abc123xyz",
  "credits_added": 500,
  "new_balance": 542
}

Mẹo: Hãy lưu trữ idempotency_key từ payload của webhook và kiểm tra nó trước khi gọi endpoint này. Điều này giúp ngăn chặn việc vô tình cấp tín dụng hai lần nếu máy chủ của bạn nhận được cùng một webhook nhiều hơn một lần.

Điều gì xảy ra với các khoản tín dụng được cấp khi đến kỳ đặt lại hàng tháng

Điều này phụ thuộc vào cách thiết lập tài khoản phụ:

  • Bán lại (tài khoản phụ trả tiền cho bạn để mua tín dụng): các khoản tín dụng bạn cấp ở đây được ghi nhận là tín dụng đã mua và được chuyển sang mỗi tháng. Bất kỳ khoản nào tài khoản phụ chưa chi tiêu sẽ vẫn nằm trong số dư.
  • Phân bổ (bạn cấp cho tài khoản phụ một hạn mức hàng tháng): số dư sẽ được nạp lại bằng hạn mức hàng tháng vào ngày đặt lại, vì vậy bất kỳ khoản nào chưa chi tiêu sẽ không được chuyển sang. Đây là thiết kế có chủ đích — hạn mức được cấp mới mỗi tháng.

Nếu bạn muốn số dư còn lại của tài khoản phụ được cộng thêm vào hạn mức hàng tháng mới thay vì thay thế nó, hãy bật tính năng chuyển tiếp tín dụng:

PUT https://api.dmchamp.com/v1/subaccounts/{subAccountUid}/limits
X-API-Key: YOUR_API_KEY
Content-Type: application/json

{
  "usageLimits": { "roll_over_to_next_month": true }
}

Nếu tài khoản phụ — hoặc gói dịch vụ mà tài khoản đó đang sử dụng — cũng có thiết lập giới hạn chuyển đổi (roll-over cap) hoặc thời hạn sử dụng, thì quá trình đặt lại sẽ diễn ra tại thời điểm các thiết lập đó được áp dụng: hạn mức được chuyển sang tháng sau sẽ bị cắt giảm theo số tháng bạn cho phép, và hạn mức không được sử dụng quá thời hạn cho phép sẽ bị loại bỏ trước khi hạn mức mới được cộng thêm. Các khoản tín dụng được cấp thông qua endpoint này, các khoản nạp lại tự động và các khoản nạp tiền của chính khách hàng là các khoản tín dụng một lần và không bao giờ bị cắt giảm. Xem Giới hạn những gì được chuyển đổi.


Ví dụ toàn diện

Một trình xử lý điển hình trông như thế này:

on POST /your-recharge-webhook:
  payload = request.body

  if seen(payload.idempotency_key):
    return 200  // already processed, ack and exit

  charge_result = your_payment_provider.charge(
    email = payload.sub_account_email,
    amount_cents = payload.total_amount_cents,
    currency = payload.price_per_credit_currency,
  )

  if not charge_result.ok:
    return 500  // platform will retry on next balance drop

  api.post("/v1/subaccounts/credits", {
    email = payload.sub_account_email,
    amount = payload.credits_requested,
    description = "Auto-recharge via " + your_provider_name,
  })

  mark_seen(payload.idempotency_key)
  return 200