开发者/功能接入
话费充值
话费充值会直接给预付费手机号充值。客户的号码会收到话费或流量,没有需要交付的卡密。和其他订单一样,费用从你在 CardV 的钱包中扣除。
相关文档:认证与签名 · 通用约定 · Webhook 通知
#流程
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 列出当前可以充值的国家。
{
"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 可以按运营商名称筛选。
{
"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_url | CardV 托管的图片,没有时为 ""。 |
- 金额都是当地金额,即手机号实际到账的金额,以当地币种计。
country不存在或格式错误时,返回 HTTP 400。
#报价
POST/api/v1/recharge/quote 返回一笔充值的价格。这个请求必须签名。
{
"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。 |
返回结果:
{
"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 给手机号充值,并从钱包扣款。这个请求必须签名。
{
"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:
{
"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)。
{
"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 列出你的充值订单,最新的在前。
GET /api/v1/recharge/orders?status=processing&limit=50&offset=0{"count": 3, "limit": 50, "results": ["..."]}- 筛选条件:
status,以及search(CardV 订单号、商户订单号或运营商名称)。 limit默认 20,最大 100,超过 100 按 100 处理。limit或offset为负数或不是数字时,返回 HTTP 400。
#订单状态
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:
{"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。