×

🛍️《微店开放平台API详解:社交电商小微商家的零成本接入路径》(附Python源码)

万邦科技Lex 万邦科技Lex 发表于2026-08-07 09:16:28 浏览27 评论0

抢沙发发表评论

结论先拍:微店开放平台对自用型应用(单店自研)是九家里最友好的“轻量零成本”档——无按量人民币扣费、无预充值、无云内/云外10倍差,企业实名+商城版店铺建自用型应用,订单/商品/库存/售后全开,QPS默认单应用约10/s(社区实测5~10/s,文档口径单应用200/店铺维度1000需提额),日调用几万次内基本零账单。 做私域小微商家ERP,正确路径是自用型AppKey+店铺OAuth+增量modified+订单推送订阅,别碰服务型ISV那套服务市场抽成。


一、微店API收费与准入真实边界(2026)

维度
自用型应用(单店自研)
服务型应用(ISV/多店)
主体要求
个人可注册,企业实名权限更高;自用型仅商城版用户
必须企业微店认证(营业执照+对公)
调用范围
仅自己注册店铺,无法跨店
授权商家数据,可在服务市场售卖
基础API费
0(无按量扣费)
0(但服务市场抽成6‰平台技术费+类目分成)
增值/敏感
OAuth私有数据(订单/地址/手机)走授权token,无单独计费
同左
预充值
QPS
默认单应用10/s(实测5~10),可提额
同,按应用等级
推送订阅
weidian_open.json回调地址可收订单消息
审核
无需平台审核,建完即用
需3工作日上线审核
官方锚点:自用型“无需授权但只能给自己注册的店铺用、接口权限高、无需平台审核”;服务型“需卖家授权、经审核后方可使用”。
和前八家对照:淘宝/拼多多/抖店有按量0.01~0.2元/百次+预充值;1688基础免费但QPS10卡脖+高级包年费;微店自用型是纯0调用费,隐形成本只在“企业认证(若走企业)+服务器ECS+开发工时”。

二、零成本接入路径(单店自研四步)

  1. 账号与认证:登录 open.weidian.com → 个人实名可做基础商品读取;拿订单必须企业实名+商城版店铺(自用型仅支持商城版用户)。

  2. 创建自用型应用:管理中心→创建应用→选“自用型”→填名称/简介/IP白名单→勾选商品管理、订单查询、库存修改、售后查询权限包(不勾订单接口返回401)。

  3. 拿Token:自用型用client_credentialsaccess_token(2h有效期,服务端缓存刷新);私有订单数据走店铺OAuth授权码换token。

  4. 订阅推送(可选但推荐):应用详情填http://域名/weidian_open.json回调,收订单变更消息,把轮询降级为5分钟补偿。


三、核心接口地图(小微商家够用)

  • 商品:weidian.item.get / weidian.item.search / shop.item.list(详情/搜索/店铺列表)

  • 订单:weidian.order.list / weidian.order.get / weidian.order.refund.list(列表/明细/售后)

  • 库存:weidian.item.stock.update(回写)

  • 发货:weidian.order.deliver(回传快递单号,无按单佣金)

  • 推送:weidian_open.json 消息订阅(订单创建/支付/发货)

注意:微店网关有两套风格,老版api.weidian.com/item/get用MD5(appsecret+拼接+appsecret),新版/v3/item/get用HMAC-SHA256,按你控制台应用“签名方式”字段为准。

四、Python:WeidianGuardedClient(自用型零成本版)

# weidian_guarded_client.py
"""
微店开放平台 自用型应用 Client(零成本路径)
- 无按量扣费,无预充值;守卫只管 QPS/签名/Token刷新/推送幂等
- 签名示例用 MD5(appsecret+排序拼接+appsecret) 老网关;若控制台选HMAC-SHA256改_sign即可
- 订单私有数据需 access_token(OAuth或client_credentials)
"""
import time, hashlib, hmac, requests, json
from typing import Dict, Optional
from threading import Lock
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
GW_OLD = "https://api.weidian.com/item/get"
GW_V3 = "https://api.weidian.com/v3"

class WeidianGuardedClient:
    def __init__(self, app_key, app_secret,
                 qps: int = 8,           # 自用型保守8/s,留余量
                 token: Optional[str] = None,
                 sign_mode: str = "md5"):  # 'md5' | 'hmac_sha256'
        self.ak = app_key
        self.ask = app_secret
        self.qps = qps
        self.tokens = {}                 # shop_id -> (token, exp)
        self.default_token = token
        self.sign_mode = sign_mode
        self._bucket = qps
        self._ts = time.monotonic()
        self._lk = Lock()

    # ---- Token管理 ----
    def get_token(self, shop_id=None) -> str:
        if shop_id and shop_id in self.tokens:
            tk, exp = self.tokens[shop_id]
            if time.time() < exp - 300:
                return tk
        # client_credentials 自用型换票
        r = requests.post("https://open.weidian.com/api/oauth2/token",
                         data={"grant_type":"client_credentials",
                               "client_id":self.ak,"client_secret":self.ask},
                         timeout=10)
        d = r.json()
        tk = d["access_token"]
        exp = time.time() + int(d.get("expires_in", 7200))
        if shop_id:
            self.tokens[shop_id] = (tk, exp)
        self.default_token = tk
        return tk

    # ---- 令牌桶 ----
    def _wait(self):
        with self._lk:
            now = time.monotonic()
            self._bucket = min(self.qps, self._bucket + (now-self._ts)*self.qps)
            self._ts = now
            if self._bucket < 1:
                time.sleep((1-self._bucket)/self.qps + 0.005)
                self._bucket = 0
            else:
                self._bucket -= 1

    # ---- 签名 ----
    def _sign(self, params: Dict) -> str:
        f = sorted((k,v) for k,v in params.items()
                  if v is not None and str(v).strip()!="" and k!="sign")
        if self.sign_mode == "hmac_sha256":
            qs = "&".join(f"{k}={v}" for k,v in f)
            return hmac.new(self.ask.encode(), qs.encode(), hashlib.sha256).hexdigest().upper()
        # md5 老网关风格
        qs = "".join(f"{k}{v}" for k,v in f)
        return hashlib.md5(f"{self.ask}{qs}{self.ask}".encode()).hexdigest().upper()

    # ---- 安全调用 ----
    def _call(self, url, method, biz, shop_id=None, max_retry=4):
        self._wait()
        token = self.get_token(shop_id) if shop_id else self.default_token
        params = {
            "app_key": self.ak,
            "access_token": token,
            "timestamp": str(int(time.time())),
            "format": "json",
            "v": "1.0",
            "method": method,
        }
        params.update(biz)
        params["sign"] = self._sign(params)
        for att in range(max_retry):
            try:
                r = requests.post(url, data=params, timeout=15)
                d = r.json()
                if d.get("errcode") not in (0, None) and "error_response" in d:
                    code = str(d.get("error_response",{}).get("code",""))
                    if code in ("40001",):  # token失效
                        self.tokens.pop(shop_id, None)
                        token = self.get_token(shop_id)
                        params["access_token"] = token
                        time.sleep(1); continue
                    if "sign" in str(d).lower():
                        raise Exception(f"签名错: {d}")
                    raise Exception(f"WD_ERR: {d}")
                return d
            except requests.RequestException:
                time.sleep(2**att); continue
        raise RuntimeError("retry exhausted")

    # 业务方法
    def get_item(self, item_id, shop_id=None):
        return self._call(GW_OLD, "weidian.item.get",
                          {"item_id": str(item_id)}, shop_id)

    def list_orders_inc(self, start, end, page=1, page_size=50, shop_id=None):
        return self._call(GW_V3+"/order/list",
                          "weidian.order.list",
                          {"start_time":start,"end_time":end,
                           "page":page,"page_size":page_size}, shop_id)

    def get_order(self, order_id, shop_id=None):
        return self._call(GW_V3+"/order/get",
                          "weidian.order.get", {"order_id":order_id}, shop_id)

    def update_stock(self, item_id, sku_id, num, shop_id=None):
        return self._call(GW_V3+"/item/stock/update",
                          "weidian.item.stock.update",
                          {"item_id":item_id,"sku_id":sku_id,"stock_num":num}, shop_id)
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
if __name__ == "__main__":
    cli = WeidianGuardedClient("AK", "AS", qps=8, sign_mode="md5")
    # 单店日调1万次内:0元
    try:
        r = cli.list_orders_inc("2026-08-01 00:00:00", "2026-08-01 00:05:00")
        print("订单增量OK", r.get("errcode"))
    except Exception as e:
        print("ERR", e)

五、零成本架构的三条铁律

  1. 自用型别升服务型:单店/同主体多店用自用型,0元;一旦建服务型上服务市场,立刻背6‰平台技术费+类目10~30%分成+企业认证,小微商家不划算。

  2. QPS别压满:默认10/s,令牌桶压8/s,订单增量5分钟一轮(单店日调用<3000次),完全在免费舒适区;别学淘宝那样搞秒级轮询,微店限流返回429不像拼多多断气但会拖慢私域体验。

  3. 推送替轮询:填weidian_open.json订阅订单消息,ERP只读回调+每30分钟order.list校对,调用量再砍90%,真正做到“零成本”而非“理论零成本”。


六、和前八家放一起的CTO结论

  • 国内5家(淘宝/京东/1688/拼多多/抖店):有按量或预充值,云内是必选项

  • 跨境(亚马逊/eBay):亚马逊暂免、eBay免费但有天调用上限。

  • 苏宁:0.1/百次统一价+库存免费+发货0.01/单。

  • 微店:自用型纯0调用费,是九家里唯一“小微商家自研ERP可以做到API账单为0”的平台,代价是生态小、文档弱、QPS不高、必须商城版。

一句话定性:微店不是“便宜”,是自用型场景下根本不进入计费回路;小微私域商家做ERP,把淘宝/拼多多/抖店那套“云内+预充值守卫”的复杂度降一档,换成“自用型AppKey+8QPS令牌桶+推送订阅”,就能在0元API费下跑稳单店日万单级同步。
要不要我把 WeidianGuardedClient 改成读 YAML 配置(多店shop_id→token缓存)+ 接 weidian_open.json 推送Consumer(Flask/webhook幂等写PG),直接拼进你前面对接九家的中台调度器,让微店成为里面唯一一个“guard只管QPS不管余额”的适配器?


群贤毕至

访客