开发者/功能接入

话费充值

话费充值会直接给预付费手机号充值。客户的号码会收到话费或流量,没有需要交付的卡密。和其他订单一样,费用从你在 CardV 的钱包中扣除。

相关文档:认证与签名 · 通用约定 · Webhook 通知

#流程

Text
GET  /recharge/countries            可以充值的国家
GET  /recharge/operators?country=US  运营商、充值类型和金额
POST /recharge/quote                你的价格,以及 300 秒内有效的 quote_token
POST /recharge/orders               下单,从钱包扣款
GET  /recharge/orders/{order_id}    轮询状态,或等待 Webhook
  • 所有路径都以 /api/v1 开头。请求头和其他接口一样。
  • 两个 POST 请求都必须签名。签名方法和 POST/orders 相同,但要用各自的路径,例如 /api/v1/recharge/quote。
  • 充值订单和礼品卡订单是分开的,请用 /recharge 系列接口查询。
  • 只提供直充。PIN 类商品(需要客户自己输入的充值码)不在此列。

#国家

GET/api/v1/recharge/countries 列出当前可以充值的国家。

JSON
{
  "count": 2,
  "results": [
    {"code": "MX", "name": "Mexico", "currency_codes": ["MXN"], "operator_count": 4, "offer_count": 37},
    {"code": "US", "name": "United States", "currency_codes": ["USD"], "operator_count": 6, "offer_count": 52}
  ]
}
  • code 是 ISO 3166-1 alpha-2 国家代码,后续请求中作为 country 传入。
  • currency_codes 是该国运营商销售所用的当地币种。
  • 运营商新增或下线时,列表会随之变化。建议每隔几个小时重新获取一次。

#运营商

GET/api/v1/recharge/operators?country=US 列出某个国家的运营商。加上 search=att 可以按运营商名称筛选。

JSON
{
  "count": 1,
  "results": [
    {
      "operator_key": "us-att",
      "name": "AT&T",
      "country": "US",
      "country_name": "United States",
      "logo_url": "https://b2b.cardv.net/api/v1/catalog-assets/3f5c...a1.png",
      "subtypes": ["airtime", "data"],
      "amount_model": "range",
      "currency_codes": ["USD"],
      "amounts": [
        {"min": "5.0000", "max": "100.0000", "currency": "USD", "subtype": "airtime", "label": "5.0000-100.0000 USD"},
        {"min": "15.0000", "max": "15.0000", "currency": "USD", "subtype": "data", "label": "15.0000 USD"}
      ],
      "offer_count": 3
    }
  ]
}
字段说明
operator_key报价和下单时传的编号,例如 us-att。请按文本保存。
subtypes可购买的类型:airtime(话费)、data(流量)或 bundle(话费加流量)。
amount_model所有金额都是固定值时为 fixed,只要有一个是范围就为 range。
amounts[]每个可选金额。min 等于 max 时为固定金额,否则可以选两者之间的任意金额。
amounts[].currency该选项的当地币种,作为 local_currency 传入。
logo_urlCardV 托管的图片,没有时为 ""。
  • 金额都是当地金额,即手机号实际到账的金额,以当地币种计。
  • country 不存在或格式错误时,返回 HTTP 400。

#报价

POST/api/v1/recharge/quote 返回一笔充值的价格。这个请求必须签名。

JSON
{
  "country": "US",
  "operator_key": "us-att",
  "amount": "10.00",
  "local_currency": "USD",
  "subtype": "airtime"
}
字段是否必填说明
country是国家列表中的国家代码。
operator_key是来自运营商列表。
amount是当地金额,用字符串传递。固定金额:必须是列出的值之一;范围金额:在 min 和 max 之间。
local_currency建议填写amount 的 ISO 4217 币种代码,取自 amounts[].currency。运营商有多个币种时请务必传。
subtype否airtime(默认)、data 或 bundle。

返回结果:

JSON
{
  "country": "US",
  "country_name": "United States",
  "operator": {"operator_key": "us-att", "name": "AT&T", "logo_url": ""},
  "subtype": "airtime",
  "local_amount": "10.0000",
  "local_currency": "USD",
  "merchant_price": "9.6200",
  "merchant_currency": "USD",
  "expires_at": "2026-09-30T08:20:30.123456+00:00",
  "quote_token": "eyJ2ZXJzaW9uIjox...:1uXyZa:8c1f..."
}
  • merchant_price 是你的钱包需要支付的金额,币种为 merchant_currency。
  • quote_token 会把这个价格锁定 300 秒,直到 expires_at。下单时请原样传回。
  • 这个令牌和你的账户绑定,也和本次的国家、运营商、类型和金额绑定。
  • 下单前请确认 local_currency 是你预期的币种。
  • 报价不会冻结资金,你可以随时重新报价。

#充值下单

POST/api/v1/recharge/orders 给手机号充值,并从钱包扣款。这个请求必须签名。

JSON
{
  "external_order_id": "SHOP-RC-20260930-0001",
  "country": "US",
  "operator_key": "us-att",
  "amount": "10.00",
  "local_currency": "USD",
  "subtype": "airtime",
  "account": "12125550100",
  "quote_token": "eyJ2ZXJzaW9uIjox...:1uXyZa:8c1f..."
}
字段是否必填说明
external_order_id是商户订单号。在你的所有订单(包括礼品卡订单)中必须唯一。
country, operator_key, amount, local_currency, subtype是与报价时传的值相同。
account是要充值的手机号:只含数字,带国家区号,不要加 + 或空格。
quote_token是来自报价,必须在过期前使用。

手机号示例:12125550100(美国)、525512345678(墨西哥)。请确认号码属于所选的运营商。充错号码的充值无法撤回。

CardV 会校验报价,立即从钱包扣除 merchant_price,然后在后台开始充值。新订单返回 HTTP 201:

JSON
{
  "idempotent_replay": false,
  "order": {
    "order_id": "O-00005678",
    "external_order_id": "SHOP-RC-20260930-0001",
    "status": "accepted",
    "status_title": "Recharge accepted",
    "poll_after_seconds": 12,
    "account": "12***00",
    "local_amount": "10.0000",
    "local_currency": "USD",
    "merchant_price": "9.6200",
    "merchant_currency": "USD",
    "...": "more fields"
  }
}
  • 请保存 order.order_id。
  • 返回的手机号会打码,不会返回完整号码。

#安全重试

商户订单号(external_order_id)可以防止重复充值。

你提交的内容返回结果
新的商户订单号HTTP 201,创建新订单并扣款。
相同的商户订单号 + 相同的充值内容HTTP 200,"idempotent_replay": true,返回原订单,不再扣款。
相同的商户订单号 + 不同的充值内容HTTP 400,错误字段为 external_order_id,不做任何处理。

"相同的充值内容"是指:国家、运营商、类型、金额和手机号都相同。系统会先识别是否为重复提交,再校验报价,所以即使 quote_token 已过期,也会返回第一次的订单。

  • 遇到超时、5xx 或连接中断时,用同一个商户订单号提交相同的内容。每次都要用新的时间戳和随机串重新签名。
  • **不要因为没收到响应就换一个新的商户订单号。**这样可能会给手机号重复充值。

#查询充值订单

GET/api/v1/recharge/orders/{order_id} 返回一笔订单。可以使用 CardV 订单号(O-00005678)。

JSON
{
  "order_id": "O-00005678",
  "external_order_id": "SHOP-RC-20260930-0001",
  "status": "processing",
  "order_status": "processing",
  "status_title": "Recharge processing",
  "status_message": "The recharge request is being processed. Delivery is not confirmed yet.",
  "next_step": "Keep this order open and wait for confirmation before placing another recharge.",
  "poll_after_seconds": 12,
  "country": "US",
  "operator": {"operator_key": "us-att", "name": "AT&T", "logo_url": ""},
  "subtype": "airtime",
  "account": "12***00",
  "local_amount": "10.0000",
  "local_currency": "USD",
  "merchant_price": "9.6200",
  "merchant_currency": "USD",
  "attempts": [{"attempt_no": 1, "status": "processing", "error_message": "", "submitted_at": "2026-09-30T08:16:02.511201Z", "completed_at": null}],
  "created_at": "2026-09-30T08:16:01.004211Z",
  "updated_at": "2026-09-30T08:16:02.611978Z",
  "...": "more fields"
}
  • 订单号不存在,或属于其他账户时,返回 HTTP 404。
  • status_title、status_message 和 next_step 是英文说明,可以展示给你的员工。
  • poll_after_seconds 是下次查询前需要等待的秒数。0 表示订单已结束。

#充值订单列表

GET/api/v1/recharge/orders 列出你的充值订单,最新的在前。

HTTP
GET /api/v1/recharge/orders?status=processing&limit=50&offset=0
JSON
{"count": 3, "limit": 50, "results": ["..."]}
  • 筛选条件:status,以及 search(CardV 订单号、商户订单号或运营商名称)。
  • limit 默认 20,最大 100,超过 100 按 100 处理。
  • limit 或 offset 为负数或不是数字时,返回 HTTP 400。

#订单状态

Text
accepted ──► processing ──► succeeded
                  │
                  ├──► manual_review ──► succeeded 或 refunded
                  │
                  └──► failed ──► refunded   (退款回到钱包)
状态是否结束怎么处理
accepted(已接受)否等待。已扣款,还没开始充值。
processing(处理中)否等待,可能需要几分钟。不要重新下单。
manual_review(人工核查)否CardV 正在和运营商核对结果。请等待。
succeeded(成功)是手机号已充值成功,请通知客户。
failed(失败)尚未充值没有成功。等待变为 refunded。
refunded(已退款)是钱已经退回你的钱包,可以重新下单。
  • 按 poll_after_seconds 查询,然后逐步延长间隔:30 秒、60 秒,之后每 5 分钟一次。注意不要超过频率限制。
  • 遇到不认识的状态,一律按"尚未完成"处理。
  • 订单尚未结束时,不要换一个新的商户订单号给同一个号码再充值。如果第一笔也成功了,这个号码就会被充值两次。

#Webhook 与退款

充值订单和其他订单一样会推送 Webhook: order.succeeded、order.failed 和 order.refunded。推送内容包含 CardV 订单号和商户订单号,items 列表为空。收到推送后,请用 GET/api/v1/recharge/orders/{order_id} 查询订单。

退款是自动的。运营商确认失败后,CardV 会把全部 merchant_price 退回你的钱包,订单变为 refunded。退款可以在商户后台的资金流水页面查看。已成功的充值不能退款,也不能取消。

#错误处理

错误格式遵循通用约定。报价或订单被拒绝时返回 HTTP 400,不会扣任何钱。

错误字段出现在怎么处理
detail报价、下单国家、运营商、类型或金额不可用。请查看运营商列表。
amount报价、下单不是数字、为零或超出范围。
local_currency报价、下单不是 3 位字母的 ISO 4217 代码。
account下单缺少手机号。
quote_token下单缺失、已过期、被改动或不匹配。查看 code 后重新报价。
balance下单在商户后台加款。
risk下单超过了订单限额,请联系 CardV。
external_order_id下单已被另一笔不同的订单使用。见安全重试。
wallet下单钱包未启用,请联系 CardV。

quote_token 类错误会附带 code:

JSON
{"code": "quote_expired", "quote_token": "This quote expired. Refresh the price and confirm again."}
code含义
quote_required没有传 quote_token。
quote_expired已超过 300 秒,请重新报价。
quote_invalid被改动过,或属于另一笔充值,请重新报价。
price_changed报价后你的价格有变化。请重新报价并确认新价格。

HTTP 403 表示凭证、签名、IP 或审核状态有问题,见认证与签名。

接入遇到问题?请发送邮件至 [email protected],并附上 Merchant ID 与订单号或请求 ID。