DM Champ Docs

子账户自动充值(自定义支付提供商)

如果您更倾向于通过自己的提供商(银行转账、本地支付网关、自定义计费系统)而非 Stripe 处理子账户额度支付,本平台提供了一对 Webhook + API,以便您自行运行完整的“扣费与授权”流程。非技术性概述请参阅 代理账户 页面;本页面涵盖了具体的 Webhook 有效载荷以及随后用于授予额度的 API 调用。

Webhook 只是自动充值付款方式中的一种。如果您在 SaaS 模式下连接了 Stripe 或 PayPal,平台会自动向客户保存的银行卡或 PayPal 账户扣款并授予额度,因此您无需在此页面进行任何开发。仅当您希望自行处理付款时,才需要继续阅读。


流程概览

  1. 子账户的额度余额低于其自动充值阈值。
  2. 平台调用您的 Webhook URL,并附带子账户详情及所需额度数量。
  3. 您的服务器通过您使用的任何提供商向客户扣费。
  4. 您的服务器调用 授予额度 API 为该子账户添加额度。
  5. 您的服务器响应 200 以确认收到 Webhook。

1. Webhook: agency_sub_account_auto_recharge

通过点击主侧边栏中的 SaaS 模式,选择 改用自定义支付提供商(或在配置完成后打开 自定义支付提供商 卡片),并填写 自动充值 Webhook 字段来配置 Webhook URL。

触发时机

当子账户的额度余额低于其配置的自动充值阈值时触发。

有效载荷

{
  "event": "agency_sub_account_auto_recharge",
  "sub_account_id": "<sub-account-id>",
  "sub_account_email": "[email protected]",
  "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"
}
字段 描述
event 对于此 Webhook,始终为 agency_sub_account_auto_recharge。
sub_account_id 需要额度的子账户的唯一 ID。
sub_account_email 子账户的电子邮件地址。
sub_account_name 子账户的显示名称。
agency_id 您代理账户的唯一 ID。
credits_requested 要授予的额度数量。这是您配置的充值金额,但如果余额低于阈值的程度超过了该金额所能覆盖的范围,它会自动增加到足以一次性将余额恢复到阈值以上的金额。请始终按照负载中的 credits_requested 值进行收费(并授予额度)——不要硬编码您的充值金额。
current_balance 发送 Webhook 时子账户的额度余额。
threshold 触发充值的余额阈值。
price_per_credit_cents 您配置的每额度价格(以分为单位)。
price_per_credit_currency 价格的货币(例如 usd)。
total_amount_cents 总收费金额(以分为单位)(credits_requested × price_per_credit_cents)。
timestamp 发送 Webhook 的时间(ISO 8601 格式)。
idempotency_key 此特定充值请求的唯一密钥。如果您的服务器多次收到相同的 Webhook,请使用此密钥防止重复授予额度。

重要注意事项

  • 5 分钟冷却时间 — 在针对某个子账户进行充值尝试后,即使其余额进一步下降,平台在至少 5 分钟内也不会为该子账户发送另一个 Webhook。这可以防止处理过程中的重复扣费。
  • 使用幂等键 — 在授予额度前,请务必检查 idempotency_key。如果您的服务器在授予额度后但在响应前崩溃,平台可能会在额度再次下降时重新发送 Webhook。
  • 故障是安全且可自愈的 — 如果您的 Webhook URL 无法访问或返回错误,则不会授予任何额度。只要子账户保持在阈值以下,平台就会持续重新发送 Webhook(每个 5 分钟冷却周期一次)——它不要求余额必须先回升到阈值以上再下降。单次漏掉的扣费不会再导致子账户永久无法充值。
  • 将充值金额设置在阈值或以上 — 您配置的充值金额必须大于或等于自动充值阈值,这样一次充值就能始终将余额恢复到阈值以上。(如果您需要修复某个远低于阈值的子账户,平台会自动补足全部差额——请参阅上文的 credits_requested。)

2. 授予额度 API

在您的服务器处理完支付后,调用此端点将额度添加到子账户。

请求

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

{
  "email": "[email protected]",
  "amount": 500,
  "description": "Auto-recharge via webhook"
}
字段 必填 说明
email 是 子账户的电子邮件地址(必须与您代理机构下的现有子账户匹配)。
amount 是 要授予的额度数量,最多 100,000 的整数。
description 否 描述添加额度原因的备注(显示在额度交易历史记录中)。

身份验证: 在请求头中发送您的 API 密钥 — 可以是 X-API-Key: YOUR_API_KEY 或 Authorization: Bearer YOUR_API_KEY。推荐使用请求头,因为 URL 中的密钥最终会出现在浏览器历史记录、代理日志和服务器访问日志中。?apiKey=YOUR_API_KEY 查询参数和 JSON 正文中的 apiKey 字段仍然有效,因此旧的集成可以保持不变继续运行。

响应

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

提示: 存储来自 Webhook 负载的 idempotency_key,并在调用此端点之前对其进行检查。这可以防止在您的服务器多次收到同一 Webhook 时意外重复授予额度。

每月重置时已授予的额度会发生什么

这取决于子账户的设置方式:

  • 转售(子账户向您支付额度费用):您在此处授予的额度将被记录为已购买额度,并每月结转。子账户未使用的任何额度都会保留在余额中。当您授予这些额度时,它们会从您的代理机构额度池中扣除,这与通过结账购买相同,因此请保持您的额度池充足(或在您的代理机构账户上开启“自动充值”):如果额度池无法支付该金额,调用将被拒绝并返回 402,且不会添加任何额度。
  • 分配(您为子账户提供每月限额):余额会在重置日期补充至每月限额,因此未使用的任何额度不会结转。这是有意设计的——每月都会重新授予新的限额。

如果您希望子账户的剩余余额叠加到其新的每月限额之上,而不是替换它,请开启额度结转功能:

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 }
}

如果子账户(或其所属的套餐)设置了结转上限或有效期,则重置操作会在这些规则生效时执行:结转的额度会被修剪至您允许的月份数,且超过有效期未使用的额度会在添加新额度之前被清除。通过此端点授予的额度、自动充值以及客户自行进行的充值均为一次性额度,不会被修剪。请参阅 限制结转额度。


端到端示例

典型的处理程序如下所示:

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