进阶经营 · 整理与归档

Shopify Admin API 深度应用

Shopify Admin API 深度应用 Shopify Admin API 是 Shopify 后端能力对外暴露的核心入口。所有应用、ERP / WMS 同步、自动化集成最终都要走这套 API。本文聚焦在 实战开发中真正影响项

Shopify Admin API 深度应用

Shopify Admin API 是 Shopify 后端能力对外暴露的核心入口。所有应用、ERP / WMS 同步、自动化集成最终都要走这套 API。本文聚焦在实战开发中真正影响项目成败的几个关键点:选型决策、限流机制、批量操作、版本管理。

不涉及基础调用入门——基础部分见 Shopify 官方 API 文档 。

一、GraphQL vs REST 选型

Shopify 2018 年起把 GraphQL 作为推荐方向,REST API 自 2024 年 10 月起进入维护模式(仅安全修复,不再新增功能)。新项目应优先使用 GraphQL。

关键差异

维度 REST GraphQL
数据获取 多次请求拼装 单次请求获取嵌套数据
接口稳定性 字段固定 客户端按需查询
限流计算 每秒请求数(leaky bucket) 查询成本(cost-based)
文档与示例 历史积累丰富 较新但完整
复杂查询 需要多次往返 一次往返
工具支持 curl / Postman 友好 GraphQL IDE 支持
Shopify 官方推荐 不再 推荐

何时仍可用 REST

  • 维护老代码且未在迁移计划中
  • 简单的单一资源 CRUD
  • 第三方库仅支持 REST

新功能 / 新接口(如 Cart API 部分)只在 GraphQL 提供。2025 年起的所有新项目都应优先 GraphQL

选型实例

获取 50 个产品 + 每个产品的图片 + 变体 + 库存:

  • REST:3-4 次请求(products + 每个产品的 images + variants + inventory levels)
  • GraphQL:1 次请求
query GetProductsWithDetails($first: Int!) {
  products(first: $first) {
    edges {
      node {
        id
        title
        handle
        descriptionHtml
        images(first: 10) {
          edges {
            node {
              url(transform: { maxWidth: 800 })
              altText
              width
              height
            }
          }
        }
        variants(first: 100) {
          edges {
            node {
              id
              sku
              price
              inventoryQuantity
              selectedOptions { name value }
            }
          }
        }
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

二、限流机制详解

Shopify API 限流是初学者最常踩的坑。两套 API 用不同的限流模型:

REST:Leaky Bucket

  • 每个店铺有”桶容量”40 请求(Plus 商家更高)
  • 桶以每秒 2 个请求的速度漏水
  • 请求填桶,桶满后 429 拒绝

应对

  • 监控 HTTP 响应 header X-Shopify-Shop-Api-Call-Limit(如 35/40
  • 接近 40 时延迟下次请求
  • 收到 429 后按响应 header Retry-After 等待
async function callShopifyRest(endpoint, options) {
  const response = await fetch(endpoint, options);
 
  const limit = response.headers.get('x-shopify-shop-api-call-limit');
  const [current, max] = limit.split('/').map(Number);
 
  if (current > max * 0.8) {
    await sleep(500);
  }
 
  if (response.status === 429) {
    const retryAfter = parseInt(response.headers.get('retry-after') || '2');
    await sleep(retryAfter * 1000);
    return callShopifyRest(endpoint, options);
  }
 
  return response.json();
}

GraphQL:Cost-Based

  • 每个店铺有”成本桶” 1000 点(Plus 更高)
  • 桶以每秒 50 点的速度恢复
  • 每个查询消耗一定点数(基础 1 点,连接字段按返回数量)

成本计算

query {
  products(first: 100) {  # 成本 = 100(连接字段按 first 计算)
    edges {
      node {
        id              # 0
        variants(first: 10) {  # 100 × 10 = 1000(嵌套连接乘法)
          edges {
            node { id price }
          }
        }
      }
    }
  }
}
# 总成本 ≈ 1100 点,单次查询即可超出桶容量

应对

  • 在响应 extensions.cost 中查看实际消耗
  • 减少 first 参数或拆多次查询
  • 用 fragment 但不要无限嵌套
const result = await graphqlClient.query({ query: PRODUCTS_QUERY });
console.log('Cost:', result.extensions.cost);
// {
//   requestedQueryCost: 1100,
//   actualQueryCost: 250,  // 实际成本基于返回数量
//   throttleStatus: {
//     maximumAvailable: 1000,
//     currentlyAvailable: 750,
//     restoreRate: 50
//   }
// }

Bulk Operations(批量操作)

需要导出全店数据时不要用普通分页查询——会消耗大量配额。使用 GraphQL Bulk Operations:

mutation {
  bulkOperationRunQuery(
    query: """
    {
      products {
        edges {
          node {
            id
            title
            variants {
              edges {
                node { id sku }
              }
            }
          }
        }
      }
    }
    """
  ) {
    bulkOperation { id status }
    userErrors { field message }
  }
}

特点:

  • 异步执行(提交后立即返回)
  • 完成后下载 JSONL 文件
  • 不消耗主限流配额
  • 适合全量导出(10万+ 产品的店铺)

每个店铺同时只能跑 1 个 Bulk Operation。

三、Webhook 集成

实时事件推送,避免轮询消耗 API 配额。

常用事件

事件 用途
orders/create 新订单 → 同步到 ERP
orders/updated 订单变更 → 状态同步
orders/paid 支付完成 → 触发履约
orders/fulfilled 发货 → 通知客户
products/create / update 产品同步
inventory_levels/update 库存同步
customers/create 新客户 → CRM 同步
customers/data_request GDPR 请求
customers/redact GDPR 删除
shop/redact 店铺卸载 App 后清理数据

注册 Webhook

mutation {
  webhookSubscriptionCreate(
    topic: ORDERS_CREATE
    webhookSubscription: {
      callbackUrl: "https://yourapp.com/webhooks/orders-create"
      format: JSON
    }
  ) {
    webhookSubscription { id }
    userErrors { field message }
  }
}

验证 Webhook 签名

Shopify 在 HTTP header X-Shopify-Hmac-Sha256 提供签名。所有 Webhook 必须验签,否则接口可能被恶意伪造调用:

const crypto = require('crypto');
 
function verifyWebhook(rawBody, hmacHeader, secret) {
  const calculated = crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('base64');
 
  return crypto.timingSafeEqual(
    Buffer.from(calculated),
    Buffer.from(hmacHeader)
  );
}
 
app.post('/webhooks/orders-create', (req, res) => {
  const hmac = req.headers['x-shopify-hmac-sha256'];
  const rawBody = req.rawBody; // 原始 body,未 JSON.parse
 
  if (!verifyWebhook(rawBody, hmac, SHOPIFY_WEBHOOK_SECRET)) {
    return res.status(401).send('Unauthorized');
  }
 
  // 处理事件
  res.status(200).send('OK');
});

Webhook 处理原则

  • 5 秒内响应 200:Shopify 等待时间上限 5 秒
  • 重试机制:超时或返回 5xx 时 Shopify 自动重试(最多 19 次,48 小时内)
  • 幂等性:同一事件可能被多次推送,处理函数必须幂等
  • 异步处理:复杂业务逻辑放队列异步处理,先 200 返回
app.post('/webhooks/orders-create', async (req, res) => {
  // 立即验签并响应
  if (!verifyWebhook(...)) return res.status(401).send('Unauthorized');
 
  // 入队,不阻塞响应
  await queue.add('process-order', { body: req.body });
 
  res.status(200).send('OK');
});

四、Access Token 管理

Token 类型

  • Public App Token:通过 OAuth 流程获取,给已安装店铺
  • Custom App Token:店主在后台创建,用于私有集成
  • Storefront API Token:用于面向公众的 Headless 站点

安全要点

  • 永远不要硬编码 token:用环境变量或密钥管理服务
  • 最小权限:只申请实际需要的 scope
  • 定期轮换:每 6-12 个月更换一次
  • 泄露应急:在 Shopify Partner 后台撤销并重新生成

Scopes 选择原则

Shopify 把权限拆得很细。常用:

Scope 用途
read_products / write_products 产品读写
read_orders / write_orders 订单读写
read_customers / write_customers 客户读写
read_inventory / write_inventory 库存读写
read_fulfillments / write_fulfillments 履约
read_locations 多仓
read_price_rules / write_price_rules 折扣规则

只申请必要的 scope——app review 阶段超出实际需要的 scope 会被 Shopify 退回审核。

五、API 版本管理

版本周期

Shopify 每季度发布新 API 版本,命名 YYYY-MM

  • 2024-01(1 月发布)
  • 2024-04(4 月发布)
  • 2024-07(7 月发布)
  • 2024-10(10 月发布)

每个版本支持 12 个月,过期后强制升级。未升级的应用会被自动迁移到下一个稳定版,可能触发 breaking change。

升级策略

调用版本指定

每次 API 调用都应该显式指定版本:

GET /admin/api/2024-10/products.json

GraphQL 同理:

POST /admin/api/2024-10/graphql.json

不要使用 unstable——它是开发预览,每周可能变更。

六、常见集成场景

场景 1:ERP 同步

ERP(NetSuite / 用友等) ↔ Shopify

订单方向:
  Shopify orders/create webhook
  → 服务器解析
  → 推送到 ERP
  → 创建 ERP 订单

库存方向:
  ERP 库存变化
  → 调 Shopify GraphQL mutation inventorySetOnHandQuantities
  → 同步到 Shopify

注意:

  • 双向同步必须有”事件来源”标记,避免无限循环
  • 网络异常时本地队列重试
  • 月度对账机制

场景 2:第三方仓库(3PL)集成

Shopify orders/paid → 推送到 3PL → 3PL 发货 → fulfillment 回写 Shopify

关键点:

  • 用 fulfillmentCreate mutation 完成发货
  • 物流追踪号回传:trackingInfo 字段
  • 部分发货支持(拆单)

场景 3:自定义 Checkout 后处理

orders/paid webhook
→ 异步处理:发优惠券、加会员积分、推送 CRM

不要在 Checkout 同步流程里做这些——会拖慢结账。

场景 4:客户数据导出(GDPR)

强制实现两个 Webhook:

  • customers/data_request:客户请求数据 → 30 天内提供
  • customers/redact:客户请求删除 → 30 天内删除

详见 GDPR 合规完整指南

七、性能与监控

客户端缓存

  • 不变数据(产品分类、店铺信息)本地缓存
  • 缓存失效靠 Webhook 触发(如 products/update)
  • 不要缓存订单或库存等高频变化数据

重试策略

  • 网络错误:指数退避(1s, 2s, 4s, 8s)
  • 429 限流:按 Retry-After 等待
  • 5xx 错误:最多 3 次重试

监控指标

  • API 调用次数 / 错误率 / 延迟(按 endpoint)
  • 限流命中次数
  • Webhook 接收延迟 / 失败率
  • Bulk Operation 完成时间

八、避免的常见错误

错误 1:在循环中调 API

// 反例:N+1 问题
for (const productId of productIds) {
  const product = await fetchProduct(productId); // 多次单独调用
}
 
// 正确:批量查询
const products = await graphqlQuery(`
  query { nodes(ids: ${JSON.stringify(productIds)}) { ... } }
`);

错误 2:硬编码 API 版本

// 反例:永远用 2022-01,无法升级
const url = 'https://shop.myshopify.com/admin/api/2022-01/products.json';
 
// 正确:版本变量化
const API_VERSION = process.env.SHOPIFY_API_VERSION || '2024-10';
const url = `https://shop.myshopify.com/admin/api/${API_VERSION}/products.json`;

错误 3:忽略 Webhook 重试

如果 Webhook 处理超过 5 秒,Shopify 会重试。如果处理函数不幂等,会重复处理订单。

错误 4:写权限申请过大

申请了 write_products 但只读产品。审核会被 Shopify 退回,且增加安全风险。

错误 5:不验签直接处理 Webhook

恶意第三方可以伪造 Webhook 攻击。所有 Webhook 必须验签

错误 6:缺少错误处理

// 反例
const data = await fetch(url).then(r => r.json());
 
// 正确
const response = await fetch(url);
if (!response.ok) {
  const text = await response.text();
  throw new Error(`Shopify API ${response.status}: ${text}`);
}
const data = await response.json();
if (data.errors) {
  throw new Error(`GraphQL errors: ${JSON.stringify(data.errors)}`);
}

九、开发工具

必备工具

  • Shopify CLI:脚手架与本地开发
  • GraphiQL App:Shopify 自家的 GraphQL IDE
  • Postman 与 Shopify Postman Collection:REST 测试
  • Shopify Dev Console:API 调用日志查看

推荐工具

  • GraphQL Codegen:从 GraphQL Schema 自动生成 TypeScript 类型
  • @shopify/shopify-api:官方 Node.js SDK
  • @shopify/cli-hydrogen:Headless 项目脚手架

十、相关教程