DM Champ Docs

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

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

Webhook 只是自动充值付款方式中的一种。如果您在 SaaS 模式下连接了 StripePayPal,平台会自动向客户保存的银行卡或 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": "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"
}
字段 描述
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": "customer@example.com",
  "amount": 500,
  "description": "Auto-recharge via webhook"
}
字段 必填 描述
email 子账户的电子邮件地址(必须与您代理下的现有子账户匹配)。
amount 要授予的额度数量。
description 描述添加额度原因的备注(显示在额度交易历史记录中)。

身份验证: 在请求头中发送您的 API 密钥 — 可以是 X-API-Key: YOUR_API_KEYAuthorization: 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 时意外重复授予额度。

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

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

  • 转售(子账户向您支付额度费用):您在此处授予的额度会被记录为已购买额度,并会在每月结转。子账户未使用的任何额度都将保留在余额中。
  • 分配(您为子账户提供每月限额):余额会在重置日期重新填充至每月限额,因此未使用的任何额度都不会结转。这是有意设计的 — 每月都会重新授予限额。

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

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