开发者/功能接入
商品与下单
本篇按采购流程依次说明:查余额、找商品、看价格、下单,最后获取卡密。
相关文档:认证与签名 · 通用约定 · Webhook 通知
#账户与余额
#账户信息
GET/api/v1/account 返回你的企业信息,以及 API 是否已开通。
{
"merchant_id": "M00000001",
"name": "Acme Shop",
"legal_name": "Acme Shop Ltd",
"tier": "standard",
"billing_email": "[email protected]",
"status": "active",
"kyb_status": "approved",
"api_access_enabled": true,
"default_currency": "USD"
}default_currency是你钱包的币种,所有采购价都以它结算。- CardV 审核通过你的企业资料后,
api_access_enabled变为true。
#余额
GET/api/v1/balance 返回你当前能用多少钱。
{
"currency": "USD",
"balance": "1520.4000",
"reserved_amount": "0.0000",
"available_balance": "1520.4000",
"low_balance_threshold": "200.0000",
"low_balance_notified_at": null,
"is_active": true
}available_balance是现在可用的金额,等于balance减去reserved_amount(冻结金额)。- 订单金额超过
available_balance时会被拒绝,不会扣任何钱。 low_balance_threshold是余额提醒的阈值,低于它会发邮件提醒你,在商户后台设置。- 加款请在商户后台操作。
#商品
SKU 就是一个可以购买的具体商品,例如"Steam 钱包 10 美元"。它的编号形如 S000456,报价和下单都针对 SKU。
GET/api/v1/skus 列出你可以购买的 SKU。示例:
GET /api/v1/skus?search=steam®ion=US&limit=50&offset=0筛选条件(都是可选的):
| 参数 | 示例 | 匹配内容 |
|---|---|---|
search | steam | SKU 编号、名称或品牌 |
brand | Steam | 品牌名(不区分大小写) |
region | US | 国家代码或国家名称 |
vertical | gift_card | 商品线 |
product_type | pin_code | 交付方式 |
分页:传 limit(默认 100,最大 500)和 offset。不断增大 offset 继续请求,直到 offset 达到 count。
{
"count": 7,
"limit": 1,
"results": [
{
"sku_id": "S000456",
"product_id": "P000123",
"name": "Steam Wallet 10 USD",
"product_name": "Steam Wallet US",
"brand": "Steam",
"region": "US",
"vertical": "gift_card",
"product_type": "pin_code",
"denomination_type": "fixed",
"denomination_value": "10.0000",
"face_currency": "USD",
"merchant_price": "9.2500",
"settlement_currency": "USD",
"availability": "available",
"min_quantity": 1,
"max_quantity": 100,
"required_input_schema": [],
"...": "more fields"
}
],
"filter_options": {"brands": [], "regions": [], "verticals": []}
}GET/api/v1/skus/{sku_id} 返回单个 SKU,字段与列表相同。
最常用的字段:
| 字段 | 含义 |
|---|---|
sku_id | 报价和下单时使用的编号。 |
merchant_price | 你的采购单价,币种为 settlement_currency。 |
availability | available(可售)或 unavailable(不可售)。只能购买可售的 SKU。 |
denomination_type | fixed(固定面值)或 range(自选面值),见固定面值与自选面值。 |
face_currency | 卡面币种,可能和你的钱包币种不同。 |
min_quantity、max_quantity | 一个订单行允许购买的数量范围。 |
product_type | pin_code(交付卡密)或 direct_charge(直接充值到账户)。 |
required_input_schema | 直充商品需要你提供的信息。 |
brand_logo_url、image_url | CardV 托管的图片地址,没有时为 ""。 |
description、redemption_instructions、terms | 可以直接展示给你客户的说明文字。 |
提示:
- 你只能看到已上架、且对你的账户开放的 SKU。其他 SKU 返回 404。
- 建议每 5–15 分钟同步一次商品列表;下单前务必重新报价。
filter_options列出了可用于筛选的品牌、地区和商品线。
#固定面值与自选面值
大多数 SKU 是固定面值,例如 10 USD。少数 SKU 是自选面值:由客户在一个范围内自己定金额,例如 5 到 500 USD。
| 类型 | 报价时 | 下单时 |
|---|---|---|
fixed | 传 quantity | 不传 amount |
range | 传 quantity 和 amount | 传 amount |
自选面值 SKU 的 amount 必须在 min_face_value 和 max_face_value 之间,币种为 face_currency。
#直充商品
有些商品会直接充值到客户的账户里,例如游戏账号。这类商品需要你提供账户信息,SKU 会列出需要哪些字段:
"required_input_schema": [
{"key": "player_id", "label": "Player ID", "required": true},
{"key": "server", "label": "Server", "required": false}
]下单时,在订单行的 inputs 里按 key 填写对应的值:
"inputs": {"player_id": "123456789", "server": "EU"}- 除非标明
"required": false,否则都是必填字段。 - 缺少必填值时,订单会被拒绝,并返回
items错误。 - 这些是你客户的个人信息,请妥善保护(见安全)。
#报价
报价用于查询某个数量下的当前价格。
GET /api/v1/skus/S000456/quote?quantity=2
GET /api/v1/skus/S000789/quote?quantity=1&amount=25.00{
"sku_id": "S000456",
"settlement_currency": "USD",
"merchant_price": "9.2500",
"quantity": 2,
"total_price": "18.5000",
"min_quantity": 1,
"max_quantity": 100,
"availability": "available"
}- 报价不会锁定价格,价格随时可能变化。
- 为了保护自己,下单时请把报价里的
merchant_price作为expected_unit_price一起传上来。如果价格已经变了,CardV 会拒绝这笔订单,不扣任何钱。 - 数量或金额超出范围时,返回 HTTP 400,错误字段为
quantity或amount。
#下单
POST/api/v1/orders 用于购买一个或多个 SKU,并从钱包扣款。这个请求必须签名。
{
"external_order_id": "SHOP-20260929-10001",
"items": [
{"sku_id": "S000456", "quantity": 2, "expected_unit_price": "9.2500"},
{"sku_id": "S000789", "amount": "25.00", "expected_unit_price": "23.7500"},
{
"sku_id": "S000900",
"expected_unit_price": "4.9000",
"inputs": {"player_id": "123456789"}
}
]
}| 字段 | 是否必填 | 说明 |
|---|---|---|
external_order_id | 是 | 商户订单号,1–120 个字符,必须唯一。 |
items | 是 | 一个或多个订单行。 |
items[].sku_id | 是 | 要购买的 SKU。 |
items[].quantity | 否 | 购买数量,默认 1。 |
items[].amount | 自选面值时必填 | 要购买的面值。 |
items[].expected_unit_price | 建议填写 | 报价得到的 merchant_price,请每次都传。 |
items[].inputs | 直充商品必填 | 直充商品的账户信息。 |
CardV 接受订单时,会一次性从钱包扣除订单总额,随后在后台开始发货。
返回 HTTP 201:
{
"idempotent_replay": false,
"order": {
"order_id": "O-00001234",
"external_order_id": "SHOP-20260929-10001",
"status": "accepted",
"total_amount": "46.2000",
"...": "more fields"
}
}请保存 order.order_id。下单响应里不含卡密,稍后通过查询订单获取。
#订单被拒绝
订单被拒绝时返回 HTTP 400,不会扣任何钱。根据错误字段判断原因:
| 错误字段 | 原因 | 怎么处理 |
|---|---|---|
items | 价格变了、商品不可售、金额不对或缺少直充信息 | 重新报价,改正后再提交 |
balance | 钱包余额不足 | 在商户后台加款 |
risk | 超过单笔或每日限额 | 联系 CardV |
external_order_id | 这个商户订单号已经用于另一笔不同的订单 | 见安全重试 |
价格变化时的错误示例:
{
"items": "SKU S000456 price changed from 9.2500 to 9.4100 USD; refresh quote and confirm again."
}单笔订单金额、每日采购金额和每日订单数的上限由你的账户等级决定,每日限额在 UTC 00:00 重置。具体额度请咨询 CardV。
#安全重试
商户订单号(external_order_id)可以防止重复购买。用同一个商户订单号再次提交同一笔订单,CardV 不会重复扣款,而是直接返回已经存在的那笔订单。
| 你提交的内容 | 返回结果 |
|---|---|
| 新的商户订单号 | HTTP 201,创建新订单并扣款。 |
| 相同的商户订单号 + 相同的订单内容 | HTTP 200,"idempotent_replay": true,返回原订单,不再扣款。 |
| 相同的商户订单号 + 不同的订单内容 | HTTP 400,错误字段为 external_order_id,不做任何处理。 |
"相同的订单内容"是指:订单行数量和顺序一致,每行的 SKU、数量、面值和直充信息都相同。如果传了 expected_unit_price,它必须和第一次下单时的价格一致。
系统会先识别是否为重复提交,再检查余额和价格。所以即使价格在这期间变了,重复提交也总是返回原来那笔订单。
#重试流程
没有收到明确结果时,直接把同一笔订单再提交一次即可。
用商户订单号 R 提交 POST /orders
├─ 201 或 200 → 保存 order_id,完成。
├─ 400 items / balance / risk → 订单没有创建。
│ 处理原因后重新提交,可以继续用 R。
├─ 400 external_order_id → R 已被另一笔不同的订单占用,停下来核查。
├─ 403 签名错误 → 重新签名,提交同样的内容。
├─ 429 → 等待 Retry-After 秒数后,重新签名并提交同样的内容。
└─ 超时、5xx 或连接中断
→ 用同一个 R 再提交一次同样的内容。
返回 201 说明第一次没到达,返回 200 说明第一次已成功。要点:
- 不要因为没收到响应就换一个新的商户订单号。 如果第一次请求其实已经到达,换号就会重复购买。
- 每次重新提交都要生成新的时间戳、随机串和签名,订单内容保持不变。
#查询订单
GET/api/v1/orders/{order_id} 返回订单详情、状态和卡密。
{
"order_id": "O-00001234",
"external_order_id": "SHOP-20260929-10001",
"status": "succeeded",
"currency": "USD",
"total_amount": "18.5000",
"created_at": "2026-09-29T08:15:30.123456Z",
"updated_at": "2026-09-29T08:15:41.004211Z",
"items": [
{
"sku_id": "S000456",
"product_name": "Steam Wallet US",
"quantity": 2,
"unit_price": "9.2500",
"total_price": "18.5000",
"delivery_count": 2,
"deliveries": [{"...": "see Codes below"}]
}
],
"...": "more fields"
}total_amount是订单被接受时实际扣除的金额。items[].unit_price是这笔订单锁定的单价。items[].deliveries里是完整卡密,请把整个响应当作机密数据对待。invoice_url和delivery_file_url分别是发票和卡密 CSV 的下载路径,只能在商户后台打开,用 API Key 访问会返回 HTTP 403。- 响应里还有
id(旧的数字编号,请勿使用)、events(仅供展示的处理记录)以及各订单行的发货进度,对接时可以忽略。 - 订单号不存在时返回 HTTP 404。
#订单状态与卡密
#订单状态
accepted ──► processing ──► succeeded
│
├──► partially_succeeded (部分到货,部分未到货)
│
└──► failed ──► refunded (退款回到钱包)| 状态 | 是否结束 | 怎么处理 |
|---|---|---|
accepted(已接受) | 否 | 等待。已扣款,还没开始发货。 |
processing(处理中) | 否 | 等待。不要重新下单。 |
succeeded(成功) | 是 | 读取卡密,交付给客户。 |
partially_succeeded(部分成功) | 是 | 交付已到货的部分,其余部分稍后退款。 |
failed(失败) | 尚未 | 等待变为 refunded。失败不等于已退款。 |
refunded(已退款) | 是 | 钱已经退回你的钱包。 |
如果不用 Webhook,建议这样轮询:5 秒后查一次,然后 10 秒、30 秒、60 秒,之后每 5 分钟一次,并注意不要超过频率限制。大多数订单几秒内就能完成,个别需要人工处理的可能要几个小时。
#卡密
每个已交付的单位是 items[].deliveries 里的一个对象:
{
"status": "stored",
"delivery_type": "card_pin",
"display_fields": [
{"key": "card_number", "label": "Card number", "value": "X1234", "kind": "secret"},
{"key": "pin_code", "label": "PIN", "value": "9876", "kind": "secret"}
],
"redeem_url": "",
"expiry_date": "2027-09-29",
"instructions": "Redeem at ...",
"is_masked": false
}- 把
display_fields展示给客户:每一项都有名称(label)和值(value)。redeem_url、expiry_date、instructions不为空时也一并展示。 kind为secret表示卡号、密码等机密内容,为reference表示序列号等参考信息。delivery_type表示交付形式:code、card_pin、link、code_link或qr。以后可能出现新的形式,所以请始终按display_fields来展示。link类型的redeem_url本身就是卡密,请保密。status为voided(已作废)的单位不要交付给客户。- 直充商品通常没有交付内容,状态为
succeeded即表示已充值到账。 card_number、pin_code等字段是同样内容的另一份拷贝,可能为空。- Webhook 通知里不含卡密,收到通知后请再查询一次订单。
接入遇到问题?请发送邮件至 [email protected],并附上 Merchant ID 与订单号或请求 ID。