下游代理商 API 文档

📄 版本 v1.4 📅 更新 2026-03-18 🌐 Base URL https://mama.best

一、认证与签名

1.1 获取凭证

联系平台管理员开通代理商账户,获取:

  • api_key:公开标识,每次请求必须携带
  • api_secret:私钥,用于签名,创建时一次性返回,之后不可再查,请妥善保存

1.2 Sandbox 测试环境

平台提供 Sandbox 测试账号,用于开发联调。测试账号的所有接口返回模拟数据,不调用真实上游、不消耗真实资金,接口格式与生产环境完全一致。

项目
Base URLhttps://mama.best/api/v1(与生产相同)
API Keysandbox_test_key_dd123456789abc
API Secretsandbox_test_secret_dd_abcdef1234567890abcdef1234567890abcdef123456
初始余额$1000(模拟)
文档地址https://doc.mama.best

Sandbox 特征:

  • 卡 BIN 以 SB 开头(如 SB000001
  • virtualCardId 以 SB 开头(如 SB260319...
  • 持卡人 ID 以 SB_HOLDER_ 开头
  • 余额正常扣减/退回,验证资金流转逻辑
  • 无需真实身份证信息,持卡人随意填写即可通过
⚠️ 联调完成后,将 API Key / Secret 替换为正式账号凭证即可上生产,代码无需其他改动。

Sandbox Webhook 测试

先告知平台配置你的 webhookUrl,然后调用以下接口,平台会主动向你的地址推送一条模拟事件:

POST/api/v1/sandbox/trigger-webhook
字段类型必填说明
notifyTypestring事件类型,默认 CARD_SETTLEMENT。可选:CARD_AUTH / CARD_AUTH_CANCEL / CARD_SETTLEMENT / CARD_REFUND / CARD_CANCEL / CARD_FREEZE / CARD_UNFREEZE
virtualCardIdstring卡片 ID,不传则用随机 mock ID
externalRefstring用户标识,默认 sandbox_user_001

响应中 data.httpStatus 是你的 Webhook 端点返回的 HTTP 状态码,data.response 是你端点的响应体,可用于确认接收是否正常。

⚠️ 此接口仅 Sandbox 账号可调用,生产账号调用返回 4003 错误。

1.3 请求规范

  • 请求方式:HTTPS
  • 数据格式:application/json
  • 字符编码:UTF-8

1.4 请求头(所有接口必填)

Header说明示例
X-Api-Key代理商 API Keya1b2c3d4e5f6...
X-Timestamp当前毫秒时间戳(13位)1710641234567
X-SignatureHMAC-SHA256 签名(见下方)3a7f9c2b...
Content-Type固定值application/json

1.3 签名算法

signature = HMAC-SHA256(api_secret, METHOD + PATH + TIMESTAMP + BODY)
组件说明示例
METHOD大写 HTTP 方法GETPOST
PATHpathname,不含域名,不含 Query String/api/v1/balance
TIMESTAMP与 X-Timestamp 相同的毫秒时间戳字符串1710641234567
BODY原始请求体字符串(GET 请求时为空字符串 ""{"virtualCardId":"VC..."}
⚠️
签名使用 pathname,不含 query string。
例如请求 /api/v1/cards?page=1,签名时使用 /api/v1/cards
POST body 签名使用 JSON.stringify 的原始字符串,不能格式化(不能有多余空格/换行)。

时间戳有效期

时间戳必须在当前时间 ±5 分钟 内,超时返回 401。服务端使用 timing-safe 比较防止时序攻击。

1.4 签名示例(Node.js)

const crypto = require('crypto');

function sign(secret, method, path, timestamp, body = '') {
  const pathname = path.split('?')[0]; // 去掉 query string
  const payload = `${method.toUpperCase()}${pathname}${timestamp}${body}`;
  return crypto.createHmac('sha256', secret).update(payload).digest('hex');
}

const secret = 'your_api_secret';
const timestamp = Date.now().toString();

// GET 请求示例
const signature = sign(secret, 'GET', '/api/v1/balance', timestamp, '');
const headers = {
  'X-Api-Key': 'your_api_key',
  'X-Timestamp': timestamp,
  'X-Signature': signature,
  'Content-Type': 'application/json',
};

// POST 请求示例
const body = JSON.stringify({ virtualCardId: 'VC2603172099197' });
const postSig = sign(secret, 'POST', '/api/v1/cards/info', timestamp, body);

1.5 签名示例(Python)

import hmac, hashlib, time, json, requests

def sign(secret, method, path, timestamp, body=''):
    pathname = path.split('?')[0]
    payload = f"{method.upper()}{pathname}{timestamp}{body}"
    return hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest()

API_KEY = 'your_api_key'
API_SECRET = 'your_api_secret'
BASE = 'https://mama.best'

# GET 示例
ts = str(int(time.time() * 1000))
sig = sign(API_SECRET, 'GET', '/api/v1/balance', ts)
resp = requests.get(f'{BASE}/api/v1/balance', headers={
    'X-Api-Key': API_KEY, 'X-Timestamp': ts, 'X-Signature': sig,
    'Content-Type': 'application/json',
})

# POST 示例(注意 separators 不能有多余空格)
body = json.dumps({"virtualCardId": "VC2603..."}, separators=(',', ':'))
ts = str(int(time.time() * 1000))
sig = sign(API_SECRET, 'POST', '/api/v1/cards/info', ts, body)
resp = requests.post(f'{BASE}/api/v1/cards/info', data=body, headers={
    'X-Api-Key': API_KEY, 'X-Timestamp': ts, 'X-Signature': sig,
    'Content-Type': 'application/json',
})

二、通用响应格式

{
  "code": 0,
  "data": { ... },
  "message": "success"
}
code说明
0成功
401认证失败(签名无效 / API Key 无效 / 时间戳过期 / 账号已禁用)
4001余额不足 / 业务限制
4003参数错误 / 无权限
4004资源不存在
4009重复请求(幂等冲突)
5000服务器内部错误
5001上游渠道调用失败

三、接口总览


四、externalRef 用户标识

externalRef 是代理商自定义的用户标识,用于将平台资源(卡片/持卡人/交易)与你自己系统中的用户关联。格式:任意字符串,最长 128 字符,建议使用你系统中的用户 ID,如 user_123456

接口参数位置说明
POST /cardholdersbody创建持卡人时绑定
GET /cardholders?externalRef=xxxquery按用户筛选持卡人
POST /cards/applybody开卡时绑定到卡片
GET /cards?externalRef=xxxquery按用户筛选卡片
POST /cards/transactionsbody查该用户所有卡的交易
Webhook 回调payload自动携带原始 externalRef

五、接口详情

GET 查询余额

GET/api/v1/balance

无请求参数。

{
  "code": 0,
  "data": {
    "balance": "500.00",
    "frozenBalance": "10.00",
    "currency": "USD"
  }
}
字段说明
balance可用余额(已扣除冻结)
frozenBalance提现冻结中的金额
currency固定 USD

POST 创建持卡人

POST/api/v1/cardholders

持卡人是虚拟卡的归属人,开卡时必须指定 cardholderId。一个持卡人可开多张卡。

字段类型必填说明
firstNamestring名(英文/拼音,如 San)
lastNamestring姓(英文/拼音,如 Zhang)
birthdaystring生日,格式 YYYY-MM-DD
genderstringM=男 / F=女
idTypestring证件类型(推荐 ID_CARD)
idNumberstring证件号码(身份证需 18 位真实号码)
phoneAreaCodestring手机区号,如 86(不含+号)
phonestring手机号
provincestring省份(中文,如 广东省)
citystring城市(中文,如 深圳市)
addressstring详细地址
emailstring邮箱(若提供,全局唯一)
countrystring国家代码(默认 CN)
postalCodestring邮政编码
remarksstring备注
externalRefstring自定义用户标识,最长 128 字符

idType 枚举

idType说明
ID_CARD中国身份证(推荐)
PASSPORT护照
DRIVING_LICENSE驾照
⚠️
实名校验说明:
• 身份证号必须是真实存在的号码(区划码、生日与号码一致,有公安库比对)
province / city 必须填中文(如 福建省 / 泉州市),填英文会导致审核失败
birthday 必须与身份证中间 8 位一致
email 若提供则全局唯一,同一邮箱不能注册两个持卡人

请求示例

{
  "firstName": "San",
  "lastName": "Zhang",
  "birthday": "1990-01-15",
  "gender": "M",
  "idType": "ID_CARD",
  "idNumber": "350582199001150011",
  "phoneAreaCode": "86",
  "phone": "13800138000",
  "province": "福建省",
  "city": "泉州市",
  "address": "丰泽区东海街道东海大街1号",
  "country": "CN",
  "postalCode": "362000",
  "email": "[email protected]",
  "externalRef": "user_123456"
}

响应示例

{
  "code": 0,
  "data": {
    "cardholderId": "2029856542649827330",
    "localId": "5",
    "firstName": "San",
    "lastName": "Zhang",
    "externalRef": "user_123456",
    "status": "1"
  }
}
💡
cardholderId 是开卡时必传的持卡人 ID,请妥善保存。

GET 持卡人列表

GET/api/v1/cardholders
Query 参数类型必填说明
pagenumber页码,默认 1
pageSizenumber每页数量,默认 20,最大 100
externalRefstring按用户标识筛选
{
  "code": 0,
  "data": {
    "list": [
      {
        "cardholderId": "2029856542649827330",
        "localId": "5",
        "firstName": "San",
        "lastName": "Zhang",
        "country": "CN",
        "idType": "ID_CARD",
        "idNumber": "110***011",
        "externalRef": "user_123456",
        "createdAt": "2026-03-17T06:00:00.000Z"
      }
    ],
    "total": 1
  }
}

注:idNumber 返回脱敏值(前3位 + *** + 后3位)


GET 查询可用卡 BIN

GET/api/v1/card-bins

获取当前平台支持的所有卡 BIN 列表,开卡时 cardBin 字段从此处取值。建议每次开卡前动态获取,不要写死。

无需请求参数。

响应示例

{
  "code": 0,
  "data": [
    {
      "binCode": "48331700",
      "businessScene": "AD_DELIVERY",
      "area": "US",
      "currency": "USD",
      "cardType": "VISA"
    },
    {
      "binCode": "55616701",
      "businessScene": "AD_DELIVERY",
      "area": "US",
      "currency": "USD",
      "cardType": "MASTERCARD"
    }
  ]
}

响应字段

字段类型说明
binCodestring卡 BIN 段,开卡时填入 cardBin
businessScenestring使用场景,如 AD_DELIVERY(广告投放)
areastring卡片归属地区,如 US
currencystring卡片结算币种,如 USD
cardTypestring卡组织,如 VISAMASTERCARD

POST 开卡

POST/api/v1/cards/apply
字段类型必填说明
outOrderIdstring幂等键,相同值不会重复开卡
cardBinstring卡 BIN 段,可通过 GET /card-bins 接口获取可用列表
cardAmtnumber开卡充值金额(USD),必须 > 0
cardholderIdstring持卡人 ID(创建持卡人返回的 cardholderId)
externalRefstring自定义用户标识
💡
不需要传 walletNo,平台自动使用主钱包。

请求示例

{
  "outOrderId": "order-20260317-uuid-001",
  "cardBin": "556371",
  "cardAmt": 100,
  "cardholderId": "2029856542649827330",
  "externalRef": "user_123456"
}

响应示例

{
  "code": 0,
  "data": {
    "virtualCardId": "VC2603172099197",
    "status": "0",
    "outOrderId": "order-20260317-uuid-001",
    "cardNoMasked": "****"
  }
}
⚠️
异步激活:virtualCardId 立即返回,但卡片激活(status=3)需等待平台回调(通常几秒~几分钟)。
激活后 Webhook 推送 CARD_APPLY 事件,届时可通过 cards/info 查询完整卡号和 CVV。

费用说明

从余额扣 cardAmt(充值到卡片)+ fee_apply(开卡固定费,管理员配置)。开卡失败 / cardholderId 无权限 → 自动全额退款


GET 卡片列表

GET/api/v1/cards
Query 参数类型必填说明
pagenumber页码,默认 1
pageSizenumber每页数量,默认 20,最大 100
externalRefstring按用户标识筛选
{
  "code": 0,
  "data": {
    "list": [
      {
        "virtualCardId": "VC2603172099197",
        "cardNoMasked": "5561****7969",
        "cardBin": "55616701",
        "status": 3,
        "cardBalance": "50.00",
        "totalLimit": 100,
        "expiryDate": "03/27",
        "currency": "USD",
        "externalRef": "user_123456",
        "createdAt": "2026-03-17T08:00:00.000Z"
      }
    ],
    "total": 1,
    "page": 1,
    "pageSize": 20
  }
}

卡片状态枚举

status说明
0申请中
1申请失败
2待激活
3正常使用
4已冻结
5已注销

POST 查卡详情

POST/api/v1/cards/info

实时查询卡片数据,包含完整卡号、CVV、实时余额

字段类型必填说明
virtualCardIdstring卡片 ID(VC...)
{
  "code": 0,
  "data": {
    "virtualCardId": "VC2603172099197",
    "cardNo": "5561670158047969",
    "cardBin": "55616701",
    "cvv": "446",
    "expiry": "03/27",
    "status": "3",
    "cardAmt": 100,
    "totalLimit": 100,
    "usedLimit": 20,
    "availableLimit": 80,
    "ccy": "USD"
  }
}
⚠️
判断卡片实际可用余额应使用 availableLimit,而非 cardAmt

POST 查卡号/CVV

POST/api/v1/cards/sensitive

每次调用都会记录审计日志(含调用 IP 和时间),适合前端展示卡号/CVV 的场景。

字段类型必填说明
virtualCardIdstring卡片 ID
{
  "code": 0,
  "data": {
    "cardNo": "5561670158047969",
    "cvv": "446",
    "expiry": "03/27"
  }
}
⚠️
请仅在你的服务端调用此接口,不要在前端 JS 中直接暴露 API Secret 和调用。

POST 卡片交易记录

POST/api/v1/cards/transactions

支持两种查询模式:

模式一:按卡片查(单卡)

字段类型必填说明
virtualCardIdstring✅ 二选一查单张卡的交易记录
pagenumber页码,默认 1
pageSizenumber每页数量,默认 20,最大 100

模式二:按用户查(该用户所有卡)

字段类型必填说明
externalRefstring✅ 二选一查该用户下所有卡的交易
pagenumber页码,默认 1
pageSizenumber每页数量,默认 20,最大 100
{
  "code": 0,
  "data": {
    "list": [
      {
        "id": 1,
        "cardId": 5,
        "notifyType": "CARD_SETTLEMENT",
        "payOrderId": "CT2026...",
        "amount": "20.00",
        "currency": "USD",
        "merchantName": "OPENAI *CHATGPT SUBSCR",
        "transactionTime": "2026-03-17T10:00:00.000Z",
        "status": 1,
        "createdAt": "2026-03-17T10:00:05.000Z"
      }
    ],
    "total": 4,
    "page": 1,
    "pageSize": 20
  }
}

POST 提额(向卡片充值)

POST/api/v1/cards/increase
字段类型必填说明
virtualCardIdstring卡片 ID
amountnumber提额金额(USD),必须 > 0
{
  "code": 0,
  "data": {
    "message": "提额成功",
    "virtualCardId": "VC2603172099197",
    "amount": 50
  }
}

从代理商可用余额扣 amount,充值到指定卡片。失败自动退款。不额外收手续费。


POST 降额(从卡片回收资金)

POST/api/v1/cards/decrease
字段类型必填说明
virtualCardIdstring卡片 ID
amountnumber降额金额(USD),必须 > 0
{
  "code": 0,
  "data": {
    "message": "降额成功",
    "virtualCardId": "VC2603172099197",
    "amount": 30
  }
}

从卡片取回 amount,退回代理商可用余额。降额金额不能超过卡片当前 availableLimit。不额外收手续费。


POST 申请提现

POST/api/v1/withdraw/apply
字段类型必填说明
amountnumber提现金额(USD),最小 $10
receiveAddressstringUSDT TRC20 收款地址(T 开头,34位 Base58)
{
  "code": 0,
  "data": {
    "id": 1,
    "requestNo": "AW1710641234567ABCD",
    "amount": "200.000000",
    "feeAmount": "2.000000",
    "actualAmount": "198.000000",
    "receiveAddress": "TXxxx...",
    "status": 0,
    "createdAt": "2026-03-17T05:30:00.000Z"
  }
}

提现状态枚举

status说明
0待审核
1已通过
2处理中(出金中)
3已完成
4已拒绝(余额已原路退回)

GET 提现记录

GET/api/v1/withdraw/list

返回最近 20 条记录,按创建时间倒序。

{
  "code": 0,
  "data": {
    "list": [
      {
        "id": 1,
        "requestNo": "AW1710641234567ABCD",
        "amount": "200.000000",
        "feeAmount": "2.000000",
        "actualAmount": "198.000000",
        "receiveAddress": "TXxxx...",
        "status": 3,
        "txHash": "abc123...",
        "createdAt": "2026-03-17T05:30:00.000Z"
      }
    ],
    "total": 1
  }
}

GET 扣费流水

GET/api/v1/fee-logs

返回最近 20 条记录,按创建时间倒序。

{
  "code": 0,
  "data": {
    "list": [
      {
        "id": 1,
        "feeType": "APPLY_CARD",
        "feeAmount": "2.000000",
        "balanceBefore": "502.000000",
        "balanceAfter": "500.000000",
        "relatedCardId": "VC2603...",
        "remark": "开卡费 cardBin=556371",
        "createdAt": "2026-03-17T05:00:00.000Z"
      }
    ],
    "total": 21
  }
}

feeType 枚举

feeType方向说明
APPLY_CARD开卡固定费
CARD_FUND开卡/提额充值到卡
CARD_RETURN退降额退款回余额
WITHDRAW提现手续费
REFUND退开卡失败退款

六、资金流转说明

代理商平台余额(由管理员 USDT 充值) │ ├── 开卡 ──→ 扣 cardAmt(→ 卡片 availableLimit)+ fee_apply(→ 平台收入) │ 失败自动全额退款 │ ├── 提额 ──→ 扣 amount(→ 卡片 availableLimit) │ 失败自动退款 │ ├── 降额 ←── 退 amount(← 卡片 availableLimit → 代理商余额) │ ├── 消费 ──→ 直接扣卡片额度(不经过代理商余额) │ 平台通过 Webhook 通知 │ └── 提现 ──→ 冻结 amount(balance → frozenBalance) 管理员审核 → 出金 → 完成(frozenBalance 清除) 拒绝 → 退回(frozenBalance → balance)

七、Webhook 回调

7.1 概述

当卡片发生消费、退款、状态变更等事件时,平台自动向你配置的 webhookUrl 发送 POST 回调。

  • 已配置 webhookUrl(联系管理员设置)
  • 代理商状态为启用(status=1)
  • 卡片 apiClientId 已关联(仅转发代理商自己的卡片事件)

7.2 请求格式

Header说明
X-SignatureHMAC-SHA256(apiSecret, requestBody) hex 编码
X-Timestamp毫秒时间戳
Content-Typeapplication/json
⚠️
Webhook 签名规则与 API 签名不同:Webhook 只对 body 做 HMAC(不拼接 method/path/timestamp)。
{
  "notifyType": "CARD_SETTLEMENT",
  "data": {
    "virtualCardId": "VC2603172099197",
    "payOrderId": "CT2026...",
    "externalRef": "user_123456",
    "clientId": "4",
    "clientName": "你的代理商名称",
    "settlementAmount": 20.00,
    "settlementCurrency": "USD",
    "consumptionAmount": 20.00,
    "consumptionCurrency": "USD",
    "transactionObject": "OPENAI *CHATGPT SUBSCR",
    "transactionTime": "2026-03-17 10:00:00",
    "status": "2"
  }
}

7.3 验签(必须实现)

const crypto = require('crypto');

function verifyWebhook(apiSecret, rawBody, signature) {
  // ⚠️ Webhook 签名只对 body 做 HMAC,不含 method/path/timestamp
  const expected = crypto
    .createHmac('sha256', apiSecret)
    .update(rawBody)  // 原始 body 字符串,不要 JSON.parse 后再 stringify
    .digest('hex');
  return expected === signature;
}

// Express 示例
// ⚠️ 重要:Webhook 路由必须在 express.json() 全局中间件之前注册!
// 全局 app.use(express.json()) 会提前消费 body stream,
// 导致 express.raw() 拿到空 body,签名验证永远失败。
app.post('/webhook/dd', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-signature'];
  const raw = req.body.toString();
  
  if (!verifyWebhook('your_api_secret', raw, sig)) {
    return res.status(401).json({ code: 1, message: 'invalid signature' });
  }
  
  const payload = JSON.parse(raw);
  console.log('收到回调:', payload.notifyType, payload.data);
  
  res.json({ code: 0, message: 'success' });
});
💡
10 秒内响应 HTTP 200,code 必须为 0。失败时平台自动重试,最多 3 次(间隔 0s / 5s / 10s)。

7.4 notifyType 枚举

notifyType说明建议处理
CARD_APPLY开卡成功(卡片激活)更新卡片状态为可用,通知用户
CARD_AUTH消费预授权(冻结额度)记录,可展示"处理中"消费
CARD_AUTH_FAIL授权失败(余额不足/限额等)记录日志,通知用户
CARD_AUTH_CANCEL授权撤销释放之前冻结的额度
CARD_SETTLEMENT消费结算(最终扣款)核心事件:更新交易为已完成
CARD_SETTLE_SUPPLEMENT结算补扣(实际金额>预授权)额外扣差额
CARD_SETTLE_REFUND结算退款(实际金额<预授权)退回差额
CARD_REFUND商户退款退回用户可用余额
CARD_FREEZE卡被冻结(上游风控触发,非主动操作)更新卡片状态,通知用户
CARD_UNFREEZE卡片解冻(上游风控解除)更新卡片状态,通知用户
CARD_CANCEL卡片注销(上游强制)更新状态,余额已自动退回代理商

八、完整接入流程

步骤 1:获取凭证 联系管理员创建代理商账号 → 获得 api_key + api_secret(secret 只展示一次!) → 提供你的 Webhook URL 给管理员配置 步骤 2:充值 管理员通过后台给你充值平台余额(USDT → USD) → 调用 GET /balance 确认到账 步骤 3:创建持卡人 POST /cardholders → 保存返回的 cardholderId(后续开卡必须) 步骤 4:开卡 POST /cards/apply → 保存 virtualCardId 步骤 5:等待激活 收到 Webhook CARD_APPLY → 卡片 status=3 → 调用 cards/info 或 cards/sensitive 获取完整卡号和 CVV 步骤 6:使用 用户拿到卡号 + CVV + 有效期,即可进行线上消费 消费发生时你会收到 CARD_AUTH → CARD_SETTLEMENT 回调 步骤 7:日常管理 提额 / 降额 / 查交易 步骤 8:提现 POST /withdraw/apply → 管理员审核出金(1-2 工作日)

九、注意事项

  1. 幂等性:outOrderId 相同的开卡请求只执行一次,重复请求返回原始结果;正在处理中返回 4009
  2. 金额精度:请求时可传 number 或 string;响应中金额为 string(6位小数精度),显示时建议保留 2 位
  3. 时间戳:精确到毫秒(13位),与服务器时差不超过 5 分钟
  4. 签名 body:使用 JSON.stringify 的原始字符串,不要格式化或添加多余空格
  5. 签名路径:使用 pathname,不含 query string
  6. Webhook 验签:使用 express.raw() 保留原始 body;Webhook 签名只对 body 做 HMAC,与 API 签名规则不同;若项目全局注册了 express.json(),必须将 Webhook 路由放在它之前,否则 body 被提前消费导致签名永远失败
  7. api_secret 安全:仅在服务端使用,不要写入前端代码、Git 仓库或日志
  8. 卡号安全:cards/infocards/sensitive 返回明文卡号/CVV,仅在你的服务端调用
  9. 金额上限:单次操作金额不超过 $1,000,000
  10. 提现地址:仅支持 TRC20 地址(T 开头,34位 Base58 字符)