REST API · v1

商品下单 API 接入文档

使用带「商品下单」权限的 dy_sk_ Key 和统一预付余额,按当前用户等级价查询商品、检查数量并购买兑换码。 本文给出可直接使用的请求、响应、幂等恢复和错误处理规则。

Base URL
https://api.dingyue.app/shop/v1
Authentication
Bearer dy_sk_...
Machine readable

快速开始

  1. 登录后在个人中心的「API 中心」为余额充值(单笔 $2–$10,000,通过 Taria Pay 以 USDT/USDC 支付,确认后自动到账),再创建密钥并勾选「商品下单」权限(可与「AI 代理」同时勾选)。密钥只会完整显示一次。
  2. 服务端调用 GET /products,取得商品 ID 和当前等级价。
  3. 为自己的业务订单持久化一个唯一 externalOrderId。
  4. 调用 POST /orders;请求结果不确定时复用同一参数重试。
  5. 把每个 code 和响应中的 redeemUrl 一起交付给最终用户。
最重要的规则:发送下单请求前先保存 externalOrderId。任何超时、断网或 5xx 重试都必须使用相同 externalOrderId、productId 和 qty,不能生成新值。

鉴权

除 GET /pricing、OpenAPI 和 llms.txt 外,所有接口都需要:

Authorization: Bearer dy_sk_your_api_key

权限在创建密钥时勾选:shop:read 始终包含,可读取商品与余额;下单、订单列表和订单详情都需要 shop:order;ai:proxy 对应 AI 代理,可与 shop:order 同时勾选。只勾了 AI 代理的 Key 无法访问订单数据。

权限创建后不能修改:旧 Key 不能追加 shop:order,需要时请吊销后重建。带商品下单权限的 Key 可直接消耗账户余额,只能保存在后端服务、服务器密钥或本地环境变量中。

接口一览

方法路径所需权限用途
GET/pricing无需鉴权公开价目表:已开放商品的 Lv1–Lv5 等级价与等级门槛
GET/productsshop:read查询已开放商品、当前等级价与履约状态
GET/products/:id/availability?qty=Nshop:read实时检查指定数量是否可购
GET/balanceshop:read查询统一 API 余额、预留金额与等级
POST/topupsshop:read发起余额充值,返回 USDT/USDC 付款链接 paymentUrl
GET/topups?limit=Nshop:read按时间倒序列出本账户的充值记录
GET/topups/:idshop:read查询充值状态;轮询到 credited 即已到账
POST/ordersshop:order从余额购买兑换码;externalOrderId 幂等
GET/ordersshop:order按游标查询订单,可用 externalOrderId 过滤;不返回 codes
GET/orders/:idshop:order查询订单详情;已交付订单返回 codes

金额全部使用十进制字符串,例如 "21.99",不要用二进制浮点数自行计算或比较。

商品与报价

查询当前等级价

curl https://api.dingyue.app/shop/v1/products \
  -H "Authorization: Bearer dy_sk_your_api_key"
{
  "level": {
    "level": 2,
    "scoreMicrocents": 10000000000,
    "score": "100.00",
    "nextLevel": {
      "level": 3,
      "thresholdMicrocents": 50000000000,
      "remainingMicrocents": 40000000000,
      "remaining": "400.00"
    }
  },
  "products": [{
    "id": "prod_chatgpt_plus",
    "slug": "chatgpt-plus",
    "name": "ChatGPT Plus",
    "price": "21.99",
    "currency": "USD",
    "inStock": true,
    "stockMode": "on_demand",
    "stock": null,
    "availabilityStatus": "available",
    "deliveryMode": "automatic"
  }]
}
  • price 是当前 API Key 所属账户的最终单价,不是供货成本。
  • availabilityStatus 描述当前履约状态;下单前仍应检查目标数量。
  • 客户端应保存商品 ID,不要用商品名称作为稳定标识。

检查指定数量

curl "https://api.dingyue.app/shop/v1/products/prod_chatgpt_plus/availability?qty=2" \
  -H "Authorization: Bearer dy_sk_your_api_key"
{
  "productId": "prod_chatgpt_plus",
  "qty": 2,
  "available": true,
  "unitPrice": "21.99",
  "currency": "USD",
  "stockMode": "on_demand",
  "availabilityStatus": "available",
  "deliveryMode": "automatic",
  "checkedAt": 1787600000
}

单次数量范围为 1–1000。可购性是调用时刻的结果,不构成长期库存或 SLA 保证。

余额与等级价格

curl https://api.dingyue.app/shop/v1/balance \
  -H "Authorization: Bearer dy_sk_your_api_key"
{
  "balance": "125.0000",
  "reserved": "0.0000",
  "available": "125.0000",
  "currency": "USD",
  "level": {
    "level": 2,
    "scoreMicrocents": 12500000000,
    "score": "125.00",
    "nextLevel": {
      "level": 3,
      "thresholdMicrocents": 50000000000,
      "remainingMicrocents": 37500000000,
      "remaining": "375.00"
    }
  }
}

available 等于余额减去正在处理订单的预留金额;创建订单时以可用余额为准。

等级等级分门槛
Lv1$0
Lv2$100
Lv3$500
Lv4$2,000
Lv5$10,000

等级分随 API 账户正向充值累计,只升不降;购买商品和消耗 AI API 余额不会降级。每个商品都可以配置独立的 Lv1–Lv5 价格。

余额充值

任何 Key(每把 Key 都带 shop:read)都可以为自己所属的账户充值,余额与 AI API 共用。

  1. 调用 POST /topups,金额为美元十进制字符串,范围 2.00–10000.00。
  2. 在浏览器打开返回的 paymentUrl,用 USDT/USDC 付款(1 USD = 1 USDT/USDC)。付款页约 60 分钟后过期。
  3. 轮询 GET /topups/:id,直到 status 变为 credited(其他终态为 failed、expired)。
  4. 调用 GET /balance 查看新余额。到账金额同时计入等级分。
curl -X POST https://api.dingyue.app/shop/v1/topups \
  -H "Authorization: Bearer dy_sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"amount": "25.00"}'
{
  "topup": {
    "id": "atp_xxxxxxxxxxxx",
    "status": "pending",
    "amount": "25.00",
    "currency": "USD",
    "paymentUrl": "https://checkout.example.com/...",
    "transfer": {
      "address": "0x0000000000000000000000000000000000000000",
      "amount": "25.00",
      "currencies": ["USDT", "USDC"],
      "chains": [
        {
          "chainId": 8453, "name": "Base", "currencies": ["USDC", "USDT"],
          "tokens": [
            { "symbol": "USDC", "tokenAddress": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "decimals": 6, "amountMinor": "25000000" },
            { "symbol": "USDT", "tokenAddress": "0xfde4c96c8593536e31f229ea8f37b2ada2699bb2", "decimals": 6, "amountMinor": "25000000" }
          ]
        },
        {
          "chainId": 56, "name": "BNB Smart Chain", "currencies": ["USDT"],
          "tokens": [
            { "symbol": "USDT", "tokenAddress": "0x55d398326f99059ff775485246999027b3197955", "decimals": 18, "amountMinor": "25000000000000000000" }
          ]
        }
      ],
      "expiresAt": 1787600900
    },
    "expiresAt": 1787600900,
    "createdAt": 1787600000,
    "creditedAt": null
  }
}
curl https://api.dingyue.app/shop/v1/topups/atp_xxxxxxxxxxxx \
  -H "Authorization: Bearer dy_sk_your_api_key"
  • paymentUrl 只在充值待付款且未过期时返回;到账、失败或过期后为 null。
  • 每个账户每分钟最多创建 5 笔充值,同时计入账户级每分钟请求限额。未付款时请继续使用原 paymentUrl,不要反复创建新充值。
  • 可能的错误:400 invalid_input(金额无效)、404 not_found、409 account_not_linked、429 rate_limited、502 payment_unavailable、503 recharge_unavailable。

热钱包自动付款

响应中的 transfer 不为 null 时,可以不打开付款页,直接从自己的热钱包转账:

  1. POST /topups,读取 transfer.address、transfer.amount 和 transfer.chains。
  2. 在 transfer.chains 中选一条链,再在该链的 tokens 中选一种代币,从热钱包对其 tokenAddress 调用一次 ERC-20 transfer(transfer.address, amountMinor)。
  3. 轮询 GET /topups/:id,直到 status 为 credited。
  • amountMinor 已按该代币在该链上的 decimals 换算成最小单位整数:同样 25.00 USD,在 Base(6 位小数)是 "25000000",在 BNB Smart Chain(18 位小数)是 "25000000000000000000"。不要把一条链的 amountMinor 用到另一条链或另一种代币上。
  • 某条链没有 tokens 字段时,按该链 currencies 自行查该 chainId 上 USDT/USDC 的合约地址和 decimals,一笔转出恰好 transfer.amount。
  • 该地址只属于这一笔充值,不要用于其他充值,也不要重复转账。
  • 一笔转出准确金额:少付超过 0.5% 不会入账;多付不会自动退回。
  • 只能使用列出的链和代币,且必须是 ERC-20 转账。转错网络、转错代币或转链的原生币(ETH/BNB)都不会入账,需要联系客服人工处理。
  • 不要在 transfer.expiresAt(创建后约 60 分钟)之后转账。
  • Gas 由付款方承担。热钱包私钥只保存在你方,任何情况下都不要发给我们。
  • 无法预备收款地址时 transfer 为 null;到账、失败或过期后也变为 null,此时请使用 paymentUrl。

创建订单

POST /orders 会从统一 API 余额扣款并返回兑换码。该请求可能产生真实费用。

字段要求说明
externalOrderId必填,1–64 字符你方唯一订单号,也是幂等键
productId必填来自 GET /products
qty必填,整数 1–1000购买数量
expectedUnitPrice推荐,最多两位小数锁定用户单价,变化时拒绝下单
curl -X POST https://api.dingyue.app/shop/v1/orders \
  -H "Authorization: Bearer dy_sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "externalOrderId": "merchant-order-20260825-001",
    "productId": "prod_chatgpt_plus",
    "qty": 2,
    "expectedUnitPrice": "21.99"
  }'
{
  "orderId": "aord_xxxxxxxxxxxx",
  "externalOrderId": "merchant-order-20260825-001",
  "productId": "prod_chatgpt_plus",
  "productName": "ChatGPT Plus",
  "qty": 2,
  "unitPrice": "21.99",
  "amount": "43.98",
  "currency": "USD",
  "status": "delivered",
  "codes": ["CODE-ONE", "CODE-TWO"],
  "redeemUrl": "https://redeem.example.com",
  "errorCode": null,
  "createdAt": 1787600000,
  "deliveredAt": 1787600002,
  "reused": false
}
  • reused=false 表示本次首次处理;幂等重放成功时为 true。
  • 成功响应的 code 数量应与 qty 完全一致。
  • 兑换网址可能调整,必须使用每笔响应中的 redeemUrl,不要在客户端硬编码。
  • prod_chatgpt_plus 的码只能兑换到当前为 Free 的 ChatGPT 个人账号;已有 Plus、Go 等付费套餐的账号兑换会被拒绝(account_plan_not_free,码不会被占用)。已订阅账号请选用商品列表中的已订阅账号版本(如已开放)。

0.01 USD 真实扣款测试商品

测试商品 ID 为 prod_api_order_test。它不出现在 GET /products 和 GET /pricing 中,请直接使用此 ID。

  • 正式环境真实扣款,所有等级均为 0.01 USD/件,不是免费沙箱。
  • 只支持 qty=1,其他数量返回 400 api_test_quantity。
  • 返回的是测试码,不含任何实际权益,不能交付给最终用户;兑换站会拒绝测试码。
  • 可用于验证余额扣款、发码、订单查询和 externalOrderId 幂等。重试规则与正式订单相同:复用原 externalOrderId 不会重复扣款,换新 ID 会再下一单并再次扣款。
  • 下单前可调用 GET /products/prod_api_order_test/availability?qty=1 检查可购性。
curl -X POST https://api.dingyue.app/shop/v1/orders \
  -H "Authorization: Bearer dy_sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "externalOrderId": "integration-test-001",
    "productId": "prod_api_order_test",
    "qty": 1,
    "expectedUnitPrice": "0.01"
  }'

查询订单与恢复未知结果

按你方订单号查找

curl "https://api.dingyue.app/shop/v1/orders?externalOrderId=merchant-order-20260825-001&limit=1" \
  -H "Authorization: Bearer dy_sk_your_api_key"

列表支持 limit=1..100、不透明 cursor 和 externalOrderId 过滤(去掉首尾空格后 1–64 字符,否则返回 400 bad_request)。列表不返回兑换码;需要取码时调用订单详情。

查询详情

curl https://api.dingyue.app/shop/v1/orders/aord_xxxxxxxxxxxx \
  -H "Authorization: Bearer dy_sk_your_api_key"

当状态为 delivered 时,详情会返回 codes 和 redeemUrl。

订单状态

  • processing:供应结果仍可能在确认,保留余额预留;复用原请求重试。
  • retryable:已确认可安全重试,预留已释放;仍复用原 externalOrderId。errorCode 给出上次失败原因,取值为 out_of_stock、supplier_price_changed 或 supplier_unavailable。
  • delivered:已扣款并交付,可从详情恢复兑换码。
  • failed / refunded:订单已关闭;如需重新购买,使用新的 externalOrderId。

完整 Node.js 示例

以下示例会读取实时商品价格并创建一笔真实余额订单。运行前请确认商品、数量和账户余额。

const BASE_URL = "https://api.dingyue.app/shop/v1";
const API_KEY = process.env.DINGYUE_API_KEY;

if (!API_KEY) throw new Error("Missing DINGYUE_API_KEY");

async function request(path, init = {}) {
  const response = await fetch(BASE_URL + path, {
    ...init,
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      ...(init.body ? { "Content-Type": "application/json" } : {}),
      ...init.headers,
    },
  });
  const data = await response.json();
  if (!response.ok) {
    throw Object.assign(new Error(data.message || data.error), {
      status: response.status,
      code: data.error,
      details: data.details,
    });
  }
  return data;
}

const catalog = await request("/products");
const product = catalog.products[0];
if (!product) throw new Error("No API product is currently available");

// Persist this value with your own order before sending the request.
// If the response is lost, retry with the SAME value, productId and qty.
const externalOrderId = `merchant-${crypto.randomUUID()}`;

const order = await request("/orders", {
  method: "POST",
  body: JSON.stringify({
    externalOrderId,
    productId: product.id,
    qty: 1,
    expectedUnitPrice: product.price,
  }),
});

for (const code of order.codes) {
  console.log({ code, redeemUrl: order.redeemUrl });
}

生产代码应先把 externalOrderId 与本地业务订单写入数据库,再发出 POST;不要只保存在进程内存中。

错误处理与安全重试

错误响应使用稳定的 error 码。业务逻辑应按错误码和 HTTP 状态分支,不要依赖可变的 message 文案。

{
  "error": "price_changed",
  "message": "Product price changed; refresh the quote and retry",
  "details": {
    "expectedUnitPrice": "21.99",
    "actualUnitPrice": "22.49"
  }
}
HTTP错误码处理方式
400invalid_inputPOST /orders 或 POST /topups 请求体无效(充值金额须在 2.00–10000.00 之间);修正请求后再调用,不要原样重试
400bad_request路径或查询参数无效(qty、limit、cursor、externalOrderId 过滤);修正后再调用
400api_test_quantity0.01 测试商品(prod_api_order_test)只能 qty=1
401unauthorizedAPI Key 缺失、无效、已撤销或账户不可用
402insufficient_balance可用余额不足;用 POST /topups 或个人中心充值后复用原 externalOrderId
402key_daily_limit_exceeded该 Key 当日消费达到日上限;等 UTC 零点重置或在个人中心调高限额
403insufficient_scope当前 Key 没有接口所需权限;订单接口需要创建时勾选了「商品下单」的 Key,旧 Key 不能追加权限
403key_order_limit_exceeded订单金额超过该 Key 的单笔上限;减少数量或在个人中心调高限额
404not_found商品、订单或充值记录不存在,或不属于当前账户
409price_changed用户售价变化;重新读取商品,确认新价格后用新的 externalOrderId 和新的 expectedUnitPrice 下单
409idempotency_conflict同一 externalOrderId 被用于不同商品或数量
409out_of_stock当前数量暂不可履约(下单和可购性检查都可能返回);稍后重新检查
409supplier_price_changed供货成本超过保护线(下单和可购性检查都可能返回);等待平台更新报价
409account_not_linkedPOST /topups:该 API 账户没有已注册的所有者;请登录个人中心充值
429rate_limited账户级每分钟请求数超限,与 AI 代理(/proxy/v1/*)共用同一计数;POST /topups 另限每分钟 5 次。按 Retry-After 头(同 details.retryAfterSeconds)等待后重试
500server_error服务内部错误;POST /orders 复用原 externalOrderId 退避重试,其他情况记录时间和请求参数后联系支持
502payment_unavailablePOST /topups:付款页创建失败;稍后重试
503order_in_progress已有请求正在处理;按 details.retryAfterSeconds 等待并复用原 externalOrderId
503order_commit_pending供应结果正在确认;必须复用原 externalOrderId 重试
503supplier_unavailable供货服务暂不可用;POST 重试必须复用原 externalOrderId
503supplier_not_configured平台尚未配置供货密钥;不要循环重试,联系平台处理
503ordering_paused平台已暂停新下单;不要循环重试。进行中的订单仍可复用原 externalOrderId 恢复
503recharge_unavailablePOST /topups:充值暂不可用;稍后重试
对 POST /orders 而言,即使无法判断供应端是否已经扣款,也可以安全地用原 externalOrderId 和相同参数重试。平台会恢复同一订单,不会创建第二笔采购。

安全建议

  • 不要把 API Key 或兑换码写入前端代码、公开仓库、分析事件或普通日志。
  • externalOrderId 不要包含姓名、邮箱、手机号等个人信息。
  • 仅向订单所属用户展示 codes,并把 code 与 redeemUrl 一起交付。
  • 如果怀疑 API Key 泄露,请立即在 API Hub 撤销并重新生成。