-
- 开放接口
-
-
- 面向商户系统调用的开放 API。凭证在「商户中心 / API 客户端」创建,
- 采用 X-App-Key + HMAC-SHA256 签名鉴权。
- 发货链路同时支持「返回签名链接」和「返回结构化数据」两种模式。
- /api/open/v1 仍是上游回调链路,和本页的商户 API 分开。
-
+// ---------- TOC 目录构建 ----------
+interface TocLeaf {
+ id: string
+ label: string
+ level: 3 // 接口子小节(接口地址/请求方式/...)
+}
+interface TocItem {
+ id: string
+ label: string
+ children?: TocLeaf[]
+}
+interface TocGroup {
+ title: string
+ items: TocItem[]
+}
-
,
- },
- {
- key: 'auth',
- label: '鉴权与签名',
- children:
,
- },
- {
- key: 'endpoints',
- label: '接口列表',
- children:
,
- },
- {
- key: 'delivery',
- label: '发货模式',
- children:
,
- },
- {
- key: 'status',
- label: '状态说明',
- children:
,
- },
- ]}
- />
+function buildToc(): TocGroup[] {
+ return [
+ {
+ title: '开始',
+ items: [
+ { id: 'overview', label: '概述' },
+ { id: 'auth', label: '鉴权与签名' },
+ ],
+ },
+ {
+ title: '接口',
+ items: endpoints.map((ep) => ({
+ id: `ep-${ep.key}`,
+ label: ep.title,
+ children: endpointSections.map((s) => ({
+ id: `ep-${ep.key}-${s.id}`,
+ label: s.label,
+ level: 3 as const,
+ })),
+ })),
+ },
+ {
+ title: '附录',
+ items: [{ id: 'status', label: '状态说明' }],
+ },
+ ]
+}
+
+const toc = buildToc()
+
+const pageItems = toc.flatMap((group) => group.items)
+const pageIds = new Set(pageItems.map((item) => item.id))
+
+function findPageId(id: string): string {
+ if (pageIds.has(id)) return id
+ for (const item of pageItems) {
+ if (item.children?.some((child) => child.id === id)) return item.id
+ }
+ return 'overview'
+}
+
+function getInitialHash() {
+ if (typeof window === 'undefined') return 'overview'
+ return decodeURIComponent(window.location.hash.replace(/^#/, '')) || 'overview'
+}
+
+// ---------- 锚点跳转 ----------
+function scrollToId(id: string) {
+ const el = document.getElementById(id)
+ if (!el) return
+ const root = document.getElementById('docScroll')
+ if (!root) return
+ const top = root.scrollTop + el.getBoundingClientRect().top - root.getBoundingClientRect().top - 22
+ root.scrollTo({ top, behavior: 'smooth' })
+}
+
+export default function OpenApiDocs() {
+ const initialHash = useMemo(() => getInitialHash(), [])
+ const [activePageId, setActivePageId] = useState(() => findPageId(initialHash))
+ const [activeAnchorId, setActiveAnchorId] = useState(initialHash)
+ const activeEndpoint = useMemo(
+ () => endpoints.find((spec) => `ep-${spec.key}` === activePageId),
+ [activePageId],
+ )
+
+ useEffect(() => {
+ const syncHash = () => {
+ const nextHash = getInitialHash()
+ setActivePageId(findPageId(nextHash))
+ setActiveAnchorId(nextHash)
+ }
+ window.addEventListener('hashchange', syncHash)
+ return () => window.removeEventListener('hashchange', syncHash)
+ }, [])
+
+ useEffect(() => {
+ const root = document.getElementById('docScroll')
+ if (!root) return
+
+ if (activeAnchorId !== activePageId) {
+ requestAnimationFrame(() => scrollToId(activeAnchorId))
+ return
+ }
+ root.scrollTo({ top: 0 })
+ }, [activeAnchorId, activePageId])
+
+ const activeIndex = useMemo(
+ () => pageItems.findIndex((item) => item.id === activePageId),
+ [activePageId],
+ )
+
+ const handleNav = useCallback((id: string) => {
+ const pageId = findPageId(id)
+ setActivePageId(pageId)
+ setActiveAnchorId(id)
+ if (typeof window !== 'undefined') {
+ window.history.replaceState(null, '', `${window.location.pathname}${window.location.search}#${id}`)
+ }
+ }, [])
+
+ const handleStep = useCallback(
+ (direction: -1 | 1) => {
+ const next = pageItems[activeIndex + direction]
+ if (next) handleNav(next.id)
+ },
+ [activeIndex, handleNav],
+ )
+
+ return (
+
+ {/* 左侧目录 */}
+
+
+ {/* 右侧文档 */}
+
)
}
-function OverviewTab() {
+function PageStepper({
+ currentIndex,
+ items,
+ onStep,
+}: {
+ currentIndex: number
+ items: TocItem[]
+ onStep: (direction: -1 | 1) => void
+}) {
+ const prev = items[currentIndex - 1]
+ const next = items[currentIndex + 1]
+
return (
-
+
+ onStep(-1)}
+ >
+ 上一篇
+ {prev?.label ?? '无'}
+
+ onStep(1)}
+ >
+ 下一篇
+ {next?.label ?? '无'}
+
+
+ )
+}
+
+// ---------- 各文档小节 ----------
+function SectionWrap({
+ id,
+ title,
+ desc,
+ children,
+}: {
+ id: string
+ title: string
+ desc?: ReactNode
+ children?: ReactNode
+}) {
+ return (
+
+
+ {title}
+
+ {desc ?
{desc} : null}
+ {children}
+
+ )
+}
+
+function OverviewSection() {
+ return (
+
+ 面向商户系统调用的开放 API。凭证在「商户中心 / API 客户端」创建,
+ 采用 X-App-Key + HMAC-SHA256 签名鉴权。
+ 发货链路同时支持「返回签名链接」和「返回结构化数据」两种模式。
+ /api/open/v1 仍是上游回调链路,和本页的商户 API 分开。
+ >
+ }
+ >
}
/>
-
+
(
- {v}
- ),
+ render: (v: string) => {v} ,
},
]}
/>
-
+
{baseUrl}
@@ -140,42 +349,19 @@ function OverviewTab() {
POST 请求使用 application/json
-
-
+
+
+
+
)
}
-function DeliveryModeTab() {
+function AuthSection() {
return (
-
-
-
-
- 用 delivery-link ,后端直接返回签名后的 Web 发货页地址,前端只负责跳转或复制。
-
-
- 用 delivery 获取订单数据,再按需调用 bind 和 submit 。
-
-
- 取消作废属于商户后台操作,恢复后会重新签发新有效期和新签名。
-
-
-
-
-
- )
-}
-
-function AuthTab() {
- return (
-
-
+
+
{v} },
- { title: '必填', dataIndex: 'required', width: 80, render: (v) => v ? 是 : 否 },
+ {
+ title: '必填',
+ dataIndex: 'required',
+ width: 80,
+ render: (v) => v ? 是 : 否 ,
+ },
{ title: '说明', dataIndex: 'desc' },
]}
/>
-
+
app_key 、body_sha256 、method 、nonce 、path 、timestamp
@@ -214,19 +405,21 @@ function AuthTab() {
-
- {`app_key=ak_xxx&body_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&method=GET&nonce=a1b2c3d4e5f67890&path=/api/client/v1/products×tamp=1721450000`}
+
+ {`app_key=ak_xxx&body_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&method=GET&nonce=a1b2c3d4e5f67890&path=/api/client/v1/products×tamp=1721450000`}
-
- {`# body = {"client_order_no":"shop-10001","sku":"suit_pink_sheep"}
+
+ {`# body = {"client_order_no":"shop-10001","sku":"suit_pink_sheep"}
app_key=ak_xxx&body_sha256=<实际请求 body 字节的 SHA256 十六进制>&method=POST&nonce=a1b2c3d4e5f67890&path=/api/client/v1/orders×tamp=1721450000`}
POST 签名用的 body 必须与实际发送的 body 字节级一致 ,不要签名后再改空格或字段顺序。
@@ -236,36 +429,33 @@ app_key=ak_xxx&body_sha256=<实际请求 body 字节的 SHA256 十六进制>&met
}
/>
-
+
)
}
-function EndpointsTab() {
+function EndpointSection({ spec }: { spec: EndpointSpec }) {
return (
- ({
- key: spec.key,
- label: (
-
-
- {spec.method}
-
- {spec.path}
- {spec.summary}
-
- ),
- children: ,
- }))}
- />
+
+
+
+ {spec.title}
+
+
+ {spec.method}
+ {spec.scope}
+
+
+
+
)
}
-function StatusTab() {
+function StatusSection() {
return (
-
-
+
+
-
+
{`拿到 sku(商品列表)
+ {`拿到 sku(商品列表)
↓
POST /orders 下单(幂等,Idempotency-Key = client_order_no)
↓
@@ -313,6 +506,6 @@ POST /orders 下单(幂等,Idempotency-Key = client_order_no)
GET /orders/{order_no} 轮询,或接收 order.fulfillment.updated 回调`}
}
/>
-
+
)
}