Skip to content

硬件销售订单 API(公开 + 用户)

实体硬件销售的公开下单 / 支付 / 查单、登录用户订单查询与好友码接口。与现有虚拟商品支付(/payment/*)完全隔离,独立 hw_sales_* 数据模型,不进 PaymentGateway

基础信息

  • 公开路由前缀/public/hardware-sales(独立 hardwareSalesCors,支持 GET/POST/OPTIONS;IP + 手机号双维限流)
  • 手机号 OTP 前缀/auth/phone(公开)、/my/phone(登录态)
  • 登录用户订单前缀/hardware-sales(JWT Bearer 或 OAuth orders:checkout
  • 金额单位:一律整数分;公开下单/试算不接受客户端金额字段,成交价由服务端按 SKU + 用户选择的 tierCode 重算
  • 身份前置:公开下单 / 发起支付必须携带 OAuth orders:checkout、登录 JWT 或短期手机号验证令牌 phoneToken不允许匿名落单

客户可介入阶段

登录用户只能操作自己的硬件销售订单,非 owner 与订单不存在统一返回 404,避免泄露订单存在性。

能力允许状态限制
修改收货地址fulfillmentStatus ∈ {not_paid, awaiting_stock}订单进入 preparing 或已发货后不再允许自助修改,需联系客服发送邮件处理
标记签收fulfillmentStatus = shipped只能标记一次;已 delivered 再次提交返回 409 RECEIPT_ALREADY_MARKED

端点

GET /public/hardware-sales/skus/:sku/config

公开 SKU 配置(全部可展示价格档位 + 标准价档位编码 + 币种)。

认证:无(公开)。

路径参数

参数说明
skuSKU 业务编码(如 aiboard_01_cn

响应{ sku, title, available, status, salesMode, currency, standardTierCode, currentTier?, soldOut, tiers[] }tiers 每项含 tierCode / title / priceCents / isStandard / tierKind / totalPriceCents / payNowPriceCents / topUpPriceCents / publicLimit / displayLimit绝不返回 real_limit 或 SKU 内部成本价

常见错误404 SKU 不存在;429 限流;500 服务端异常。


POST /auth/phone/send-otp

发送手机号验证码(短信发送层当前为 mock provider,阿里云凭据就绪即切换)。

认证:无(公开)。内存级 IP 限流 + DB 维度手机号冷却 / IP 每小时限流。

请求{ phone: string, lang?: string }

响应{ success: true, message, expiresIn, retryAfter }

常见错误400 手机号格式无效;429 限流(带 Retry-After);502 发送失败。


POST /auth/phone/verify

校验验证码 + 自动注册 / 登录 + 兜底默认 Team/Project + 签发会话与手机号令牌。

认证:无(公开)。

请求{ phone: string, code: string, lang?: string }

响应{ success: true, data: { user, team, project, session: { accessToken, tokenType, expiresIn }, phoneToken, created } }。其中 phoneToken 为短期手机号验证令牌,供公开下单 / 发起支付使用。

常见错误400 手机号 / 验证码无效或过期;429 尝试次数过多;500 服务端异常。


已登录用户绑定手机号

已登录用户补绑手机号(必须验证 OTP)。

本端点的权威定义见 POST /my/phone/bind;这里仅说明它在硬件公开下单链路中的用途,避免重复维护同一路由定义。

认证:JWT Bearer Token。

请求{ phone: string, code: string }

响应{ success: true, data: { phone, phoneToken } }

常见错误400 手机号 / 验证码无效;401 未认证;409 手机号已被他人绑定;429 尝试过多。


POST /public/hardware-sales/orders

公开下单。金额服务端权威重算,body 中任何 amount / price / cents 字段一律拒绝。

认证:OAuth orders:checkout、登录 JWT 或 phoneToken(三选一,缺一不可)。IP + 手机号双维限流。

请求{ sku, tierCode, discountCode?, phoneToken?, address: { receiverName, receiverPhone?, province, city, district?, addressLine, postalCode? }, channel?, lang?, utm? }

响应{ success: true, data: { orderNo, sku, tierCode, amountCents, originalAmountCents, discountCents, finalAmountCents, discountCodeApplied, currency, paymentStatus, fulfillmentStatus, expiresAt } }tierCode 是真实成交档位;如果用户选择的档位不可用、过期或售罄,请求失败,不会静默换档。amountCentsfinalAmountCents 均为实付金额;优惠只落订单级 discountCents,明细快照仍为档位原价。

常见错误400 地址 / 参数非法 / 请求体包含金额字段;401 身份缺失或无效;404 SKU 不存在;409 档位不可用、售罄或优惠码不可用(无效 / 过期 / 不适用 / 已抢完 / 个人限用 / 未达门槛);429 限流;500 服务端异常。


POST /public/hardware-sales/orders/quote

公开订单试算。复用服务端优惠计算逻辑,不落单、不占库存名额、不占优惠码名额;下单仍会在锁内二次校验。

认证:可匿名调用;如果已带 OAuth orders:checkout、登录 JWT 或 phoneToken,后端会按该身份做试算。匿名试算只用于输入优惠码后的价格预览,不代表最终 checkout 一定成功;下单仍必须带 OAuth/JWT/phoneToken,并会在锁内按真实用户再次校验个人限次、库存和优惠名额。试算使用独立限流桶,默认 10/min,不消耗下单 / 支付额度;限流至少包含 IP,已认证请求会结合手机号,所有请求都会结合优惠码 hash。

请求{ sku, tierCode, discountCode?, phoneToken? }discountCode 为空时返回原价试算,不视为无效码。

响应{ success: true, data: { status, sku, tierCode, originalAmountCents, discountCents, finalAmountCents, discountCodeApplied, currency, discountDetails? } }

有效优惠码试算成功时(status=okdiscountCodeApplied=true),discountDetails 会返回下单前可公开展示的优惠条款:

json
{
  "title": "朋友圈 F 码",
  "refundPolicyNote": "退款后好友奖励同步失效",
  "capacityLock": true,
  "shippingNote": "预计 2026 年 8 月发货"
}

title 来自优惠规则标题,refundPolicyNote 来自优惠规则退款条款,capacityLock 来自优惠规则产能锁定标记,shippingNote 来自当前成交档位发货说明。

status 可能值:ok / discount_not_applied / sku_not_found / sku_not_available / tier_not_available / sold_out。优惠码无效、过期、不适用、名额耗尽、个人限用、未达门槛等细因不会在公开试算响应中区分;此时 discountCents=0discountCodeApplied=falsefinalAmountCents=originalAmountCents。用户选择的档位不可用、过期或售罄时返回 tier_not_available,不会静默换档。

匿名试算无法判断当前用户是否已经达到个人使用上限;如果优惠码设置了个人限次,匿名结果只表示该码对当前 SKU/档位和全局名额仍可预览,最终以下单时的身份校验为准。

无效、过期、不适用、名额耗尽、个人限次、未达门槛或未输入优惠码时不会返回 discountDetails。公开响应不会包含 tierIddiscountRuleIddiscountCodeId、顶层 capacityLock、剩余额度或精确失败原因等内部字段。超过试算限流返回 429 RATE_LIMITED,带 Retry-AfterX-RateLimit-* 响应头。


POST /public/hardware-sales/orders/:orderNo/pay

为指定订单生成微信支付信息。支持按客户端环境显式选择支付形态(不靠 User-Agent 推断):桌面扫码(Native QR)或微信内置浏览器直接调起收银台(JSAPI)。

认证:OAuth orders:checkout、登录 JWT 或 phoneToken,且身份必须与订单归属匹配(否则统一返回 ORDER_NOT_FOUND,不泄露存在性)。

路径参数

参数说明
orderNo订单号

请求体(可选 JSON;不传 body 时保持 Native 扫码行为不变

字段类型必填说明
clientPayScenestringdesktop_browser(默认,Native 扫码)/ wechat_in_app(微信内 JSAPI)/ mobile_browser(暂不支持,返回明确错误)
clientIdstringwechat_in_app 必填OAuth App client_id,用于定位公众号凭据(与 JSSDK 签名同源)
wechatAuthCodestringwechat_in_app 必填公众号网页授权 snsapi_base 返回的 code,服务端换取 openid
returnUrlstring保留字段(H5 支付阶段使用),当前忽略

响应(Native 扫码,clientPayScene 缺省或 desktop_browser

json
{ "success": true, "data": { "paymentNo": "HWSP...", "paymentMode": "native_qr", "codeUrl": "weixin://wxpay/bizpayurl?...", "qrCodeUrl": null, "paymentUrl": null, "jsapiPayParams": null, "amountCents": 29900, "currency": "CNY", "expiresAt": "..." } }

响应(微信内 JSAPI,clientPayScene='wechat_in_app'

json
{ "success": true, "data": { "paymentNo": "HWSP...", "paymentMode": "wechat_jsapi", "jsapiPayParams": { "appId": "wx...", "timeStamp": "1779459004", "nonceStr": "...", "package": "prepay_id=wx...", "signType": "RSA", "paySign": "..." }, "amountCents": 29900, "currency": "CNY", "expiresAt": "..." } }

jsapiPayParams 可直接传给 WeixinJSBridge.invoke('getBrandWCPayRequest', ...) 调起收银台;timeStamp 为秒级字符串,package 固定 prepay_id= 前缀。若走 JSSDK 的 wx.chooseWXPay(...),注意其入参字段名为小写 timestamp,需自行做 timeStamp → timestamp 映射(其余字段同名)。调起结果不可作为支付成功依据,仍以订单查询 / 回调推进为准。

常见错误401 身份缺失;404 订单不存在或无权;409 订单状态不可支付;429 限流;400 INVALID_PAYMENT_SCENE 场景取值非法;400 PAYMENT_SCENE_NOT_SUPPORTED 普通手机浏览器暂不支持(请在微信中打开);400 WECHAT_AUTH_REQUIRED 微信内支付缺 clientId/wechatAuthCode401 WECHAT_AUTH_FAILED 授权 code 失效(需重新授权);503 NOT_CONFIGURED 微信渠道未配置或公众号 AppID 与支付渠道不一致。


POST /public/hardware-sales/orders/lookup

手机号 + 订单号公开查单。必须同时匹配(手机号命中订单 phone 或收货 receiver_phone),错误手机号查不到。

认证:无(公开)。IP + 手机号 + 订单号维度限流。

请求{ phone: string, orderNo: string }

响应{ success: true, data: { orderNo, currency, amountCents, paymentStatus, fulfillmentStatus, orderStatus, stage, stageLabel, createdAt, paidAt, carrier, trackingNo, shippedAt, address } }。物流字段仅发货后暴露;地址部分脱敏(无门牌 / 姓氏打码 / 无手机号)。

常见错误400 参数非法;404(统一 ORDER_NOT_FOUND_OR_PHONE_MISMATCH,订单不存在与手机号不符返回完全相同错误);429 限流。


GET /hardware-sales/orders

登录用户订单列表(仅本人)。

认证:JWT Bearer Token 或 OAuth orders:checkout

请求:Query limit / offset / paymentStatus / orderStatus(均可选)。

响应{ success: true, data: { orders[], pagination: { total, limit, offset } } }

orders[] 包含订单号、支付 / 履约状态、amountCentsdiscountCents、创建时间等用户本人可见摘要;不返回内部订单 UUID、优惠规则 ID、优惠码 ID 或 provider raw。

常见错误401 未认证;500 服务端异常。


GET /hardware-sales/orders/:orderNo

登录用户订单详情(仅 owner,非 owner / 不存在统一 404)。

认证:JWT Bearer Token 或 OAuth orders:checkout

路径参数

参数说明
orderNo订单号

响应:订单 + 明细 + 地址(手机号脱敏)+ 支付状态 + 发货 / 物流 + 权益状态 + 用户可见事件时间线。

常见错误400 缺少订单号;401 未认证;404 不存在或无权。


POST /hardware-sales/discount-codes/derive

登录用户基于自己的一笔已支付优惠订单生成好友码。好友码仍是普通硬件销售优惠码,可被公开 quote / create 订单流程消费。

认证:JWT Bearer Token 或 OAuth orders:checkout,且必须是来源订单 owner。

请求

json
{
  "sourceOrderNo": "HWS202607010001",
  "friendSharePercent": 20,
  "tagline": "可选推荐语"
}

friendSharePercent 必须为 10/20/.../100。来源订单优惠金额必须是整元;1 元优惠只能设置 friendSharePercent=100。同一来源订单只能生成一个好友码。

响应

json
{
  "success": true,
  "data": {
    "code": "FRDABCD123",
    "status": "active",
    "sourceOrderNoMasked": "HWS...0001",
    "baseDiscountCents": 10000,
    "friendDiscountCents": 2000,
    "selfRewardCents": 8000,
    "currency": "CNY",
    "tagline": "可选推荐语",
    "expiresAt": "2026-09-29T00:00:00.000Z"
  }
}

好友码默认 maxUses=100perPersonLimit=1、有效期 90 天。后端按创建时 split 固化 metadata,下单时复制到 discount_snapshot

常见错误400 参数非法;401 未认证;404 来源订单不存在或无权;409 来源订单未支付、未使用优惠、优惠不是整元、重复派生或朋友优惠不足 1 元。


GET /hardware-sales/discount-codes/mine

查询当前用户创建的好友码列表。

认证:JWT Bearer Token 或 OAuth orders:checkout

响应{ success: true, data: { codes[] } }codes[]code / status / sourceOrderNoMasked / baseDiscountCents / friendDiscountCents / selfRewardCents / maxUses / usedCount / paidCount / expiresAt / createdAt


GET /hardware-sales/discount-codes/:code/orders

查询某个好友码产生的订单流转状态。仅好友码创建者可访问。

认证:JWT Bearer Token 或 OAuth orders:checkout

响应{ success: true, data: { code, orders[] } }

orders[] 只返回脱敏流转字段:redemptionId / orderNoMasked / maskedBuyerPhone / paymentStatus / fulfillmentStatus / orderStatus / paidAt / deliveredAt / refundStatus / settlementEligibility / eligibleAt / friendDiscountCents / selfRewardCents / createdAt

隐私约束:不返回完整手机号、姓名、地址、物流单号、内部 UUID、provider raw、discountRuleIddiscountCodeIdcapacityLock


PATCH /hardware-sales/orders/:orderNo/address

登录用户修改本人硬件销售订单收货地址。仅允许在备货前修改,即 fulfillmentStatus ∈ {not_paid, awaiting_stock};进入 preparing / shipped / delivered / cancelled 后返回 409 ORDER_STATE_INVALID,需要联系客服发送邮件处理。

认证:JWT Bearer Token 或 OAuth orders:checkout,且必须是订单 owner。

路径参数

参数说明
orderNo订单号

请求

json
{
  "receiverName": "张三",
  "receiverPhone": "13800001234",
  "province": "广东省",
  "city": "深圳市",
  "district": "南山区",
  "addressLine": "科技园路 1 号",
  "postalCode": "518000"
}

响应{ success: true, data: { address } }。返回地址中的手机号仍按用户详情口径脱敏。

常见错误400 地址参数非法;401 未认证;404 不存在或无权;409 ORDER_STATE_INVALID 当前状态不可自助改地址。


POST /hardware-sales/orders/:orderNo/receipt

登录用户将本人硬件销售订单标记为已签收。仅允许发货后操作,即 fulfillmentStatus = shipped;接口会原子推进到 delivered 并写入用户来源的 receipt_confirmed 审计事件。

认证:JWT Bearer Token 或 OAuth orders:checkout,且必须是订单 owner。

路径参数

参数说明
orderNo订单号

请求:空 body。

bash
curl -X POST "https://block2-api.wainao.chat/hardware-sales/orders/HWS202606250001/receipt" \
  -H "Authorization: Bearer <access_token>"

响应{ success: true, data: { order } },其中 order.fulfillmentStatus='delivered'order.deliveredAt 为签收时间。

常见错误401 未认证;404 不存在或无权;409 ORDER_STATE_INVALID 尚未发货或当前状态不可签收;409 RECEIPT_ALREADY_MARKED 已签收,不能重复标记。


POST /webhook/hardware-sales

微信 Native 支付成功 / 失败异步回调(硬件销售专用入口,out_trade_no 前缀 HWSP绝不进 PaymentGateway)。

认证:微信 v3 验签(wechatpay-* header)。

请求:微信回调原始 JSON 报文。

响应:处理成功回 { code: 'SUCCESS' }(含已幂等 / 订单未找到);验签 / 校验失败回 { code: 'FAIL' } 让微信重试。严格校验金额(无容差)+ 币种 CNY;幂等推进 paid 并触发权益生成。


POST /webhook/hardware-sales-refund

微信退款异步回调(按 out_refund_noHWSR 前缀路由,独立退款 webhook,不进 PaymentGateway)。

认证:微信 v3 验签。

请求:微信退款回调原始 JSON 报文。

响应:成功回 { code: 'SUCCESS' };失败回 { code: 'FAIL' }。幂等推进退款状态并回收对应权益。


相关文档

  • 功能说明:docs/features/hardware/10-sales-order.md
  • 后台运营 API:apps/docs-site/docs/api/admin-hardware-sales.md

AI Workflow Editor