返回首页


对接最佳实践

1. 商品同步策略

  • 使用 定时任务(如宝塔面板的计划任务)定时调用 goods_list + goods_detail 同步商品信息
  • 同步内容包括:库存、价格(作为成本价)、上下架状态、SKU 规格
  • 建议同步频率:每 5~10 分钟 一次,避免过于频繁
  • 本地存储远程商品 ID 的映射关系,用于下单时查找对应的主站商品

2. 下单流程建议

  • 先扣本地,再调主站:本地用户支付成功后,先在你自己的系统中标记已付款,然后再调用 order_buy 向主站下单
  • 商户单号唯一:out_trade_no 必须唯一且不可重复,建议使用你系统的订单号
  • 重试安全:因为幂等机制,同一个 out_trade_no 重复调用不会多扣费
  • 超时处理:如果请求超时,不要放弃。先调用 order_query(用 out_trade_no)查询是否已下单成功

3. 人工发货商品的轮询策略

  • order_buy 返回 status=1 时,表示需要人工发货
  • 建议使用定时任务每 3~5 分钟 批量调用 order_query 查询未完成订单
  • status ≥ 2content 非空时,表示发货完成,同步发货内容到本地
  • 也可以在用户前台点击"查看卡密"时即时调用 order_query,实现用户触发式轮询

4. 价格策略建议

  • guest_price 是你的采购成本价(已根据你的会员等级折扣后),建议在此基础上加价后作为你本地的销售价
  • 加价方式可以是固定金额(如每件加 5 元)或百分比加价(如加价 20%)
  • 建议同时关注 market_price(原价)用于本地的划线价展示

5. SKU 规格对接要点

  • 多规格商品的 sku 值由规格值 ID 组合而成,格式为 id1-id2
  • 对接端需保存"本地规格 ↔ 远程规格"的映射关系
  • 每次商品同步时,需比对远程 spec 数据以更新本地规格名称和 ID 映射
  • 下单时将用户选择的本地规格翻译回远程 sku 值传给 order_buy

6. 错误处理

  • 记录所有 API 调用的请求和响应日志,方便排查问题
  • 对接失败时将错误信息写入本地订单记录,方便管理员查看和补单
  • 区分可重试错误(网络超时、服务器临时不可用)和不可重试错误(密钥无效、余额不足、商品下架)

完整 PHP 对接示例

以下是一个封装好的 PHP 对接类,你可以直接复制使用:

DockingClient.php
<?php
/**
 * DCShop 同系统对接客户端
 * 使用方法:
 *   $client = new DockingClient('https://主站域名', 'YOUR_API_KEY');
 *   $goods = $client->goodsList(1, 1, 20);        // 获取分类1的商品
 *   $detail = $client->goodsDetail(12);            // 获取商品12的详情
 *   $order = $client->orderBuy(12, 1, '0', 'MY001'); // 下单
 *   $query = $client->orderQuery('订单号');          // 查询订单
 *   $info = $client->userInfo();                    // 查询余额
 */
class DockingClient {
    private $baseUrl;
    private $apiKey;
    private $timeout;

    public function __construct($siteUrl, $apiKey, $timeout = 30) {
        $this->baseUrl = rtrim($siteUrl, '/') . '/user/api.php';
        $this->apiKey = $apiKey;
        $this->timeout = $timeout;
    }

    /** 获取商品分类 */
    public function goodsCategory() {
        return $this->get('goods_category');
    }

    /** 获取商品列表 */
    public function goodsList($cid = 0, $page = 1, $limit = 20) {
        $params = ['page' => $page, 'limit' => $limit];
        if ($cid > 0) $params['cid'] = $cid;
        return $this->get('goods_list', $params);
    }

    /** 获取商品详情 */
    public function goodsDetail($goodsId) {
        return $this->get('goods_detail', ['id' => $goodsId]);
    }

    /** 下单购买 */
    public function orderBuy($goodsId, $quantity, $sku, $outTradeNo) {
        return $this->post('order_buy', [
            'goods_id'     => $goodsId,
            'quantity'     => $quantity,
            'sku'          => $sku ?: '0',
            'out_trade_no' => $outTradeNo,
        ]);
    }

    /** 查询订单(用主站订单号) */
    public function orderQuery($orderId) {
        return $this->get('order_query', ['order_id' => $orderId]);
    }

    /** 查询订单(用商户单号) */
    public function orderQueryByTradeNo($outTradeNo) {
        return $this->get('order_query', ['out_trade_no' => $outTradeNo]);
    }

    /** 查询账户信息 */
    public function userInfo() {
        return $this->get('user_info');
    }

    // ─── 内部方法 ───

    private function get($action, $params = []) {
        $params['action'] = $action;
        $params['api_key'] = $this->apiKey;
        $url = $this->baseUrl . '?' . http_build_query($params);

        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => $this->timeout,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
        ]);
        return $this->execute($ch);
    }

    private function post($action, $data = []) {
        $url = $this->baseUrl . '?action=' . $action;
        $data['api_key'] = $this->apiKey;

        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => http_build_query($data),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => $this->timeout,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
        ]);
        return $this->execute($ch);
    }

    private function execute($ch) {
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($error) {
            return ['code' => -1, 'msg' => '网络错误: ' . $error, 'data' => ''];
        }

        $result = json_decode($response, true);
        if (!is_array($result)) {
            return ['code' => -1, 'msg' => '响应解析失败 (HTTP ' . $httpCode . ')', 'data' => ''];
        }

        return $result;
    }
}

使用示例:完整的下单与轮询流程

example_usage.php
<?php
require_once 'DockingClient.php';

$client = new DockingClient('https://货源站域名', 'YOUR_API_KEY');

// 1. 检查余额
$info = $client->userInfo();
if ($info['code'] !== 0) {
    die('鉴权失败: ' . $info['msg']);
}
echo "当前余额: ¥" . $info['data']['money'] . "\n";
echo "会员等级: " . $info['data']['level_name'] . "\n";

// 2. 查看商品详情
$detail = $client->goodsDetail(12);
if ($detail['code'] !== 0) {
    die('获取商品失败: ' . $detail['msg']);
}
$goods = $detail['data'];
echo "商品: " . $goods['title'] . "\n";
echo "采购价: ¥" . $goods['skus'][0]['guest_price'] . "\n";
echo "库存: " . $goods['stock'] . "\n";

// 3. 下单购买
$myOrderNo = 'DOCK' . date('YmdHis') . mt_rand(1000, 9999);
$order = $client->orderBuy($goods['id'], 1, '0', $myOrderNo);
if ($order['code'] !== 0) {
    die('下单失败: ' . $order['msg']);
}

$remoteOrderId = $order['data']['order_id'];
$status = (int)$order['data']['status'];
echo "主站订单号: " . $remoteOrderId . "\n";

// 4. 判断是否需要轮询
if ($status >= 2) {
    // 自动发货,直接拿到卡密
    echo "发货内容:\n" . $order['data']['content'] . "\n";
} else {
    // 人工发货,需要定时轮询
    echo "订单等待人工发货,开始轮询...\n";
    for ($i = 0; $i < 60; $i++) {  // 最多轮询 60 次,共 5 分钟
        sleep(5);
        $query = $client->orderQuery($remoteOrderId);
        if ($query['code'] === 0 && (int)$query['data']['status'] >= 2) {
            echo "发货完成!\n";
            echo "发货内容:\n" . $query['data']['content'] . "\n";
            break;
        }
        echo "第 " . ($i + 1) . " 次轮询,状态: 待发货\n";
    }
}

商品类型说明

主站的商品有不同类型,对接时需了解各类型的发货特性:

类型标识名称发货方式对接说明
duli 独立卡密 自动发货 order_buy 返回 status=2content 中直接包含卡密
guding 固定卡密 自动发货 与独立卡密类似,自动返回发货内容
xuni 虚拟服务 人工发货 order_buy 返回 status=1,需要等待主站管理员人工处理后通过 order_query 轮询获取发货内容。
⚠️ 注意:content 可能有模板占位文字,必须以 status ≥ 2 为准!
无论商品类型是什么,对接端的处理逻辑都是统一的:status 字段判断发货状态。自动发货商品会立即返回 status=2,人工发货商品返回 status=1 后需要轮询。

常见问题 FAQ

Q1: 如何获取 API 密钥?

在主站注册账号后,联系主站管理员开通 API 对接权限。管理员会为你的账号生成 api_key,并在后台设置你可对接的商品范围。

Q2: API 有调用频率限制吗?

建议合理控制调用频率。商品同步建议 5~10 分钟一次;订单轮询建议 3~5 分钟一次。过于频繁的调用可能被主站限流或拉黑。

Q3: 下单后超时没有收到响应怎么办?

不用担心!直接使用同一个 out_trade_no 重新调用 order_buy,系统会自动识别并返回之前的订单结果,不会重复扣费。或者调用 order_queryout_trade_no 查询。

Q4: 为什么有些商品在列表中看不到?

可能的原因:

  • 商品未上架(is_on_shelf = 0
  • 商品未开启对接权限(allow_dock = 0
  • 商品已删除

Q5: 如何判断商品是否需要人工发货?

不需要提前判断。统一走 order_buy 下单,根据返回的 status 字段判断即可。status=2 就是已完成,status=1 就需要轮询 order_query

Q6: 我是非 DCShop 系统,能对接吗?

完全可以!本 API 是标准的 HTTP + JSON 接口,任何语言和系统都可以对接。你只需要:

  1. 能发送 HTTP GET/POST 请求
  2. 能解析 JSON 响应
  3. 有定时任务能力(用于商品同步和订单轮询)

Q7: 多规格商品如何选择 SKU 下单?

先调用 goods_detail 获取 specskus 数据。spec 包含规格维度和选项,skus 包含每种组合的库存与价格。用户选择后,将对应规格值的 id- 拼接传给 order_buysku 参数。