跳到主内容
快讯直播
AI智模界
教程

给 Dots 智能体开钱包:限额、白名单与代付对账

这篇教程带你给 Dots 智能体单独开一个“会花钱”的钱包:绑定支付方式、设置单笔与日限额、配置允许交易的商户白名单,最后让 Dots 实际完成一笔代付,并把账单核对清楚、把退款路径走通。

这篇能做出什么(成果先行)

跟着做下来,你会得到四样东西:

1. 一个隔离的智能体钱包:和你的个人账户分开,出问题可以单独冻结,不影响其他业务。

2. 一套能限制支出的规则:单笔上限 200 元、每日累计 1000 元、每天最多 20 笔,超出直接拒付而不是等人审批。

3. 一份商户白名单:只允许在物流、SaaS 订阅等指定商户扣款,其余一律拦下。

4. 一条完整的闭环:Dots 发起一笔代付 → 支付成功 → 拉流水对账 → 发起部分退款 → 确认账目回到初始状态。

一个具体场景:Dots 在客服会话里帮用户补寄快递,需要代付 39 元运费。这笔钱不能刷到公司主账户上,也不能让它哪天被诱导着去买别的东西,于是走智能体钱包 + 白名单 + 限额。

前置条件清单

动手前先确认这些,缺一项后面就会卡住:

  • 钱包平台的账号与权限:需要能创建钱包、绑定支付方式、配置消费策略的管理员权限。普通成员账号通常只能看不能改。
  • 一个沙箱或测试环境:先用测试卡号跑通全流程,再切生产。测试环境的接口地址、密钥、商户编号和生产是两套,别混用。
  • 可用的支付方式:企业卡或对公账户,确认它支持平台要求的验证方式(小额打款验证、3DS 验证等)。
  • Dots 的运行环境:能改工具(tool)配置、能读到环境变量、能发 HTTPS 请求。本地跑也行。
  • 三个环境变量:WALLET_API_BASE、WALLET_API_KEY、WALLET_ID。密钥只放环境变量或密钥管理服务,不要写进代码仓库。
  • 一条内部审批口径:超过多少金额需要人工确认、谁负责看每日流水。技术上能设限额,制度上也要有人盯。

```bash

建议写进 .env,且 .env 加入 .gitignore

export WALLET_API_BASE="https://<钱包平台接口域名>"

export WALLET_API_KEY="<你的 API Key>"

export WALLET_ID="<创建后返回的钱包 ID>"

```

接口路径、字段名、金额单位会随平台版本变化,以官方文档当前版本为准。下面的请求体按常见命名书写,照着改字段名即可。

第一步:先把花钱规则写成一张表

不要一上来就调接口。先想清楚“允许花多少、花给谁”,因为限额和名单是要提交给钱包平台的配置,改起来比写代码慢。

场景单笔上限每日上限每日笔数允许商户
客服补寄运费50 元300 元10物流服务商
工具订阅续费200 元200 元2指定 SaaS 商户
其他全部拒绝——空

把“其他”写成全部拒绝,是这套方案能成立的关键:白名单模式下,未列出的商户默认拦截,而不是默认放行。

第二步:创建钱包

新建一个专供 Dots 使用的钱包,名称里带上用途和负责人,方便日后审计。

```bash

curl -sS -X POST "$WALLET_API_BASE/v1/wallets" \

-H "Authorization: Bearer $WALLET_API_KEY" \

-H "Content-Type: application/json" \

-H "AI 词典:Idempotency">Idempotency-Key: create-wallet-dots-$(date +%Y%m%d)" \

-d '{

"name": "dots-agent-ops",

"type": "agent",

"owner": "team-ops",

"currency": "CNY",

"metadata": {

"agent": "dots",

"purpose": "customer-service-reimbursement"

}

}'

```

返回里会带钱包 ID 和状态。状态要等到 active 才能收付款,刚创建时可能是 pending,需要做一次身份或主体信息确认。

```bash

查询钱包状态

curl -sS "$WALLET_API_BASE/v1/wallets/$WALLET_ID" \

-H "Authorization: Bearer $WALLET_API_KEY" | jq '.status, .balance'

```

第三步:绑定支付方式

钱包是“壳”,真正出钱的是绑定的支付方式。绑卡通常分两步:提交卡信息拿到一个临时 token,再用 token 完成绑定。

```bash

1) 生成支付方式 token(在服务端调用,不要把卡号传进前端或智能体)

curl -sS -X POST "$WALLET_API_BASE/v1/payment-methods/tokens" \

-H "Authorization: Bearer $WALLET_API_KEY" \

-H "Content-Type: application/json" \

-d '{

"type": "card",

"card": {

"number": "<卡号>",

"exp_month": 12,

"exp_year": 2030,

"cvc": "<安全码>",

"holder_name": "<持卡人>"

}

}'

2) 用 token 绑定到钱包

curl -sS -X POST "$WALLET_API_BASE/v1/wallets/$WALLET_ID/payment-methods" \

-H "Authorization: Bearer $WALLET_API_KEY" \

-H "Content-Type: application/json" \

-d '{

"token": "<上一步返回的 token>",

"set_default": true

}'

```

对公账户走的是另一条路:提交开户信息后,平台会打一笔小额到账让你回填金额,验证完成后才能用。这一步的到账时间不可控,别把它排在发布前一天做。

绑定完成后,平台一般会做一次授权检查(冻结一小笔再释放)。看到这笔记录不要慌,它不是扣款。

第四步:设置单笔与日限额

限额挂在钱包上,智能体侧改不了,这是它比“在提示词里写‘不要超过 200 元’”可靠的地方。

```bash

curl -sS -X PUT "$WALLET_API_BASE/v1/wallets/$WALLET_ID/limits" \

-H "Authorization: Bearer $WALLET_API_KEY" \

-H "Content-Type: application/json" \

-d '{

"currency": "CNY",

"limits": {

"per_transaction_max": 20000,

"per_transaction_min": 100,

"daily_max": 100000,

"daily_txn_count_max": 20,

"monthly_max": 2000000

},

"on_exceed": "reject"

}'

```

几点说明:

  • 金额单位:上面的数字按“分”写。如果平台用元,要整体除以 100。这是最容易出错的地方,先在沙箱下 1 元试一笔确认单位。
  • on_exceed:设成 reject 直接拒付;也有平台支持 review 转人工。做自动化流程建议先 reject,跑稳了再对小额场景放宽。
  • 日限额的“日”:按平台所在时区还是本地时区?跨零点前后下单会看到“明明没花超却被拒”,先问清楚。
  • 降额立即生效,升额可能需要复核,所以初始额度别设太低,免得半夜改不动。

第五步:配置商户白名单

白名单两种写法:按商户 ID,或按商户类别码(MCC)。前者精确,后者覆盖广但粗。建议以商户 ID 为主,MCC 作为兜底约束。

```bash

curl -sS -X PUT "$WALLET_API_BASE/v1/wallets/$WALLET_ID/merchant-policy" \

-H "Authorization: Bearer $WALLET_API_KEY" \

-H "Content-Type: application/json" \

-d '{

"mode": "allowlist",

"allow": {

"merchant_ids": ["mch_logistics_demo", "mch_saas_demo"],

"mcc": ["4215", "7372"]

},

"deny": {

"merchant_ids": [],

"countries": ["**"]

},

"require_verified_merchant": true,

"block_cross_border": true

}'

```

要点:

  • mode 必须是 allowlist。默认模式常常是 open,不显式改就等于没设。
  • 商户 ID 要拿真实的,从你已签约的商户后台取,不要照抄教程里的示例值。
  • 开启 block_cross_border,能挡掉大部分套现和误操作。
  • 白名单变更后,已经在途的授权不受影响,只有新交易按新规则走。改完最好等一分钟再测。

第六步:把钱包接进 Dots

Dots 通过工具调用来花钱。给它一个明确的工具定义,参数越窄,出错越少。不要暴露通用的“调用任意接口”能力。

```json

{

"name": "wallet_pay",

"description": "为当前用户代付一笔费用。仅在用户明确同意金额后调用。",

"parameters": {

"type": "object",

"properties": {

"amount": { "type": "integer", "description": "金额,单位为分" },

"currency": { "type": "string", "enum": ["CNY"] },

"merchant_id": { "type": "string", "description": "商户 ID,必须来自白名单" },

"order_id": { "type": "string", "description": "内部订单号,用于对账" },

"reason": { "type": "string", "description": "代付原因,写入备注" }

},

"required": ["amount", "currency", "merchant_id", "order_id", "reason"]

}

}

```

对应的执行函数(Python 示例):

```python

import os

import uuid

import requests

def wallet_pay(amount: int, currency: str, merchant_id: str,

order_id: str, reason: str) -> dict:

resp = requests.post(

f"{os.environ['WALLET_API_BASE']}/v1/wallets/{os.environ['WALLET_ID']}/payments",

headers={

"Authorization": f"Bearer {os.environ['WALLET_API_KEY']}",

"Content-Type": "application/json",

幂等键由业务单号派生,重试不会重复扣款

"Idempotency-Key": f"pay-{order_id}",

},

json={

"amount": amount,

"currency": currency,

"merchant_id": merchant_id,

"reference": order_id,

"description": reason,

"capture": True,

},

timeout=15,

)

resp.raise_for_status()

return resp.json()

```

Idempotency-Key 由 order_id 派生,是这一步最重要的设计:Dots 重试、网络超时重发、用户连点两次,都不会变成两笔扣款。

第七步:跑通第一笔代付

先在沙箱里用一笔小金额试。

```bash

curl -sS -X POST "$WALLET_API_BASE/v1/wallets/$WALLET_ID/payments" \

-H "Authorization: Bearer $WALLET_API_KEY" \

-H "Content-Type: application/json" \

-H "Idempotency-Key: pay-order-20240501-0001" \

-d '{

"amount": 3900,

"currency": "CNY",

"merchant_id": "mch_logistics_demo",

"reference": "order-20240501-0001",

"description": "客服补寄运费",

"capture": true

}'

```

预期返回里能看到 status: succeeded(有的平台先返回 processing,靠 webhook 通知最终状态)和平台侧的 payment_id。

立刻把 payment_id 写回你自己的订单表,别只存在日志里。对账时它就是唯一能把两边对上的东西。

然后再验三条拦截规则,确认它们真的生效:

```bash

1) 超单笔限额:应被拒

amount 改成 30000(300 元,超过 200 元上限)

2) 非白名单商户:应被拒

merchant_id 改成 mch_unknown

3) 重复提交:应返回同一笔结果,不产生第二次扣款

用同一个 Idempotency-Key 再发一次

```

三条都符合预期,说明限额和白名单是真的在钱包侧生效,不是靠智能体自觉。

第八步:对账

对账做三件事:拉流水、做匹配、看差异。

```bash

拉取某天的钱包流水(分页,注意 page_size 上限)

curl -sS -G "$WALLET_API_BASE/v1/wallets/$WALLET_ID/transactions" \

-H "Authorization: Bearer $WALLET_API_KEY" \

--data-urlencode "from=2024-05-01T00:00:00+08:00" \

--data-urlencode "to=2024-05-01T23:59:59+08:00" \

--data-urlencode "type=payment,refund" \

--data-urlencode "page_size=100" \

| jq -r '.items[] | [.payment_id, .reference, .amount, .currency, .status] | @csv' \

> wallet_20240501.csv

```

匹配用 reference(你的订单号)做左连接:

```python

import csv

def reconcile(wallet_csv: str, orders: dict) -> None:

seen = set()

with open(wallet_csv, newline="") as f:

for row in csv.DictReader(f):

ref = row["reference"]

seen.add(ref)

order = orders.get(ref)

if order is None:

print(f"[钱包有、内部无] {ref} {row['amount']}")

elif int(row["amount"]) != order["amount"]:

print(f"[金额不一致] {ref} 钱包={row['amount']} 内部={order['amount']}")

elif row["status"] != "succeeded":

print(f"[状态异常] {ref} {row['status']}")

for ref in orders.keys() - seen:

print(f"[内部有、钱包无] {ref}")

差异分三类:钱包有内部无、内部有钱包无、金额或状态不一致

```

每日跑一次,把三类差异输出到值班群。差异不要自动冲平,先人工看,前几周大概率是你的 reference 没写进去,或者分页没拉全。

第九步:退款路径

退款要分全额和部分,还要确认退款后限额是否回补。

```bash

全额退款

curl -sS -X POST "$WALLET_API_BASE/v1/payments/<payment_id>/refunds" \

-H "Authorization: Bearer $WALLET_API_KEY" \

-H "Content-Type: application/json" \

-H "Idempotency-Key: refund-<payment_id>-full" \

-d '{"amount": 3900, "reason": "requested_by_customer"}'

部分退款(比如只退一半)

curl -sS -X POST "$WALLET_API_BASE/v1/payments/<payment_id>/refunds" \

-H "Authorization: Bearer $WALLET_API_KEY" \

-H "Content-Type: application/json" \

-H "Idempotency-Key: refund-<payment_id>-partial-1" \

-d '{"amount": 1950, "reason": "partial_service"}'

```

退款要盯的几件事:

  • 原路退回:钱回到绑定的支付方式,不是钱包余额。用户看到的到账时间取决于发卡行,平台侧显示“已退款”不等于用户已经收到。
  • 多次部分退款不能超过原额,靠 Idempotency-Key 和累计校验双重保证。
  • 限额是否回补:多数实现里,退款不会冲减当天的“已用额度”,也就是当天退了钱也不能立刻再花出去。这条一定要实测确认,否则会出现“明明退了款还是被拒”的困惑。
  • 对账时把退款单独算一类:退款流水通常是负向金额或独立类型,匹配逻辑里要覆盖,否则每天都会冒出假差异。

跑完这九步,做一次完整回归:付 39 元 → 对账匹配 → 全额退款 → 再对账确认净额为 0,且当天已用额度没有异常回补。

常见坑与排错

现象常见原因处理方式
一直拒付,提示 amount_invalid金额单位搞错,元当成分或分当成元沙箱下 1 元试一笔,确认单位
白名单商户也被拒mode 没显式设成 allowlist,或商户 ID 用了测试环境的值查策略回显,核对商户 ID 来源
同一订单扣了两次没传或每次重试都换了 Idempotency-Key幂等键由订单号派生,全局固定
钱包显示成功,商户侧没收到只做了授权(authorize)没做请款(capture)确认 capture: true 或补调请款接口
跨零点被拒日限额按平台时区重置,与你以为的“今天”不一致明确时区口径,跨零点前后避开批量任务
对账天天有差异分页没拉全 / 退款类型没纳入匹配加总数校验,把 refund 纳入匹配
回调重复处理未做事件去重,或未验签按事件 ID 去重,回调必须验签
密钥泄露风险密钥写进代码、日志或提示词只放环境变量,日志脱敏

排错顺序建议:先看钱包侧返回的错误码和 decline_code,再看策略配置回显,最后才怀疑 Dots 的工具参数。绝大多数问题在策略配置那一层就能定位。

下一步建议

1. 加一层人工确认:金额超过某个阈值时,让 Dots 生成一条待确认请求,走 IM 或工单审批后再执行,而不是直接付。

2. 做额度预警:当日已用额度到 80% 时推送提醒,别等被拒了才发现。

3. 给每类业务开独立钱包:客服补寄、订阅续费、采购试用各一个,限额和白名单分别设,出了问题只冻结一个。

4. 把对账做成每日任务:定时拉流水、跑匹配、输出差异报表,连续两周零差异再考虑放开额度。

5. 沉淀一份运维手册:谁有权改限额、改完怎么验证、异常怎么冻结钱包、退款争议找谁,写下来比记在脑子里可靠。

最后提醒一句:这套流程的价值不在于“让智能体自己花钱”,而在于把花钱的边界放在智能体改不动的地方。限额、白名单、幂等键、对账,这四样做扎实了,再谈自动化才有意义。具体接口细节与费率规则,以官方文档当前版本为准。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。