开发者/功能接入

商品与下单

本篇按采购流程依次说明:查余额、找商品、看价格、下单,最后获取卡密。

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

#账户与余额

#账户信息

GET/api/v1/account 返回你的企业信息,以及 API 是否已开通。

JSON
{
  "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 返回你当前能用多少钱。

JSON
{
  "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。示例:

HTTP
GET /api/v1/skus?search=steam&region=US&limit=50&offset=0

筛选条件(都是可选的):

参数示例匹配内容
searchsteamSKU 编号、名称或品牌
brandSteam品牌名(不区分大小写)
regionUS国家代码或国家名称
verticalgift_card商品线
product_typepin_code交付方式

分页:传 limit(默认 100,最大 500)和 offset。不断增大 offset 继续请求,直到 offset 达到 count。

JSON
{
  "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。
availabilityavailable(可售)或 unavailable(不可售)。只能购买可售的 SKU。
denomination_typefixed(固定面值)或 range(自选面值),见固定面值与自选面值。
face_currency卡面币种,可能和你的钱包币种不同。
min_quantity、max_quantity一个订单行允许购买的数量范围。
product_typepin_code(交付卡密)或 direct_charge(直接充值到账户)。
required_input_schema直充商品需要你提供的信息。
brand_logo_url、image_urlCardV 托管的图片地址,没有时为 ""。
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 会列出需要哪些字段:

JSON
"required_input_schema": [
  {"key": "player_id", "label": "Player ID", "required": true},
  {"key": "server", "label": "Server", "required": false}
]

下单时,在订单行的 inputs 里按 key 填写对应的值:

JSON
"inputs": {"player_id": "123456789", "server": "EU"}
  • 除非标明 "required": false,否则都是必填字段。
  • 缺少必填值时,订单会被拒绝,并返回 items 错误。
  • 这些是你客户的个人信息,请妥善保护(见安全)。

#报价

报价用于查询某个数量下的当前价格。

HTTP
GET /api/v1/skus/S000456/quote?quantity=2
GET /api/v1/skus/S000789/quote?quantity=1&amount=25.00
JSON
{
  "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,并从钱包扣款。这个请求必须签名。

JSON
{
  "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:

JSON
{
  "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这个商户订单号已经用于另一笔不同的订单见安全重试

价格变化时的错误示例:

JSON
{
  "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,它必须和第一次下单时的价格一致。

系统会先识别是否为重复提交,再检查余额和价格。所以即使价格在这期间变了,重复提交也总是返回原来那笔订单。

#重试流程

没有收到明确结果时,直接把同一笔订单再提交一次即可。

Text
用商户订单号 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} 返回订单详情、状态和卡密。

JSON
{
  "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。

#订单状态与卡密

#订单状态

Text
accepted ──► processing ──► succeeded
                  │
                  ├──► partially_succeeded   (部分到货,部分未到货)
                  │
                  └──► failed ──► refunded   (退款回到钱包)
状态是否结束怎么处理
accepted(已接受)否等待。已扣款,还没开始发货。
processing(处理中)否等待。不要重新下单。
succeeded(成功)是读取卡密,交付给客户。
partially_succeeded(部分成功)是交付已到货的部分,其余部分稍后退款。
failed(失败)尚未等待变为 refunded。失败不等于已退款。
refunded(已退款)是钱已经退回你的钱包。

如果不用 Webhook,建议这样轮询:5 秒后查一次,然后 10 秒、30 秒、60 秒,之后每 5 分钟一次,并注意不要超过频率限制。大多数订单几秒内就能完成,个别需要人工处理的可能要几个小时。

#卡密

每个已交付的单位是 items[].deliveries 里的一个对象:

JSON
{
  "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。