← 文档索引

pay 支付对接开发指南

jiu 门店在应用内购买/续费授权:付款走 pay 收款服务,付成功后 pay 回调 jiu,jiu 直接给门店续期。
v1.0 · 2026-07-03 · 面向 jiu 后端开发 · pay 侧已就绪(下单+签名+webhook 推送已实现验证)· 本文档 = jiu 侧要实现的部分
给开发者:pay 服务(收款、支付宝渠道、签名鉴权、支付成功 webhook 推送)已开发并联调验证完毕。你只需实现 jiu 这一侧的 4 块:① 购买接口 ② webhook 接收器 ③ 续期逻辑 ④ 查单兜底。接口契约、签名算法、权益映射见下,照着写即可。

1. 名词与地址

pay 服务地址https://pay.51yanmei.com
pay 下单接口POST /api/v1/orders
pay 查单接口GET /api/v1/orders/{out_trade_no}
pay 套餐列表GET /api/v1/products(含 biz_code)
jiu 回调接收器(你要实现POST /api/v1/pay/callback
共享密钥双向共用一把 HMAC 密钥,存 Bitwarden,两边经环境变量注入(pay: BIZ_JIU_SECRET;jiu 侧自定,同值)

2. 整体流程

jiu 客户端(已登录, 知道 shop_id) jiu 后端 pay 服务 支付宝 │ │ │ │ ①选套餐, 点购买 ──────────────────────►│ │ │ │ ②建 Purchase(pending, shop_id, biz_code) │ │ │ 调 pay 下单 (product_id + biz_ref=purchase_id, 签名) ─►建订单 │ │ │◄──── {pay_url} ─────────────│ │ │◄────────── 返回 pay_url ──────────────│ │ │ ③打开 pay_url 付款 (PC扫码/手机拉App) ───────────────────────────────────────────────────►付款 │ │ ④支付宝→pay 入账 ✓ │ │ │◄── ⑤pay 签名 webhook ───────│ │ │ ⑥验签→幂等→按 biz_code 给 shop 续期→回 SUCCESS │ ④'客户端跳回 return_url / 刷新 → 已续期 ✓│ │ │

3. 签名算法(两个方向都用它)

签名串(4 段用换行 \n 连接,再 HMAC-SHA256,最后 base64):

sign = base64( HMAC_SHA256( secret, system + "\n" + timestamp + "\n" + nonce + "\n" + rawBody ) )

请求头

Header说明
X-Pay-System固定 jiu
X-Pay-TimestampUnix 秒(服务端校验 ±5 分钟窗口)
X-Pay-Nonce随机串(如 uuid)
X-Pay-Sign上面算出的签名

rawBody = HTTP 请求体的原始字节(验签/签名都对同一份原始 body,勿先反序列化再重拼)。

方向:jiu→pay 下单 由 jiu 签名、pay 验签;pay→jiu 回调 由 pay 签名、jiu 验签。两边同一把密钥、同一算法。

Go 参考实现(与 pay 侧 util.HMACSign 完全一致)

// 签名(下单时调用)/ 验签(收 webhook 时对比)
func hmacSign(secret string, parts ...string) string {
    m := hmac.New(sha256.New, []byte(secret))
    m.Write([]byte(strings.Join(parts, "\n")))
    return base64.StdEncoding.EncodeToString(m.Sum(nil))
}
// 验签:hmac.Equal(([]byte)hmacSign(secret, "jiu", ts, nonce, rawBody), got)

4. jiu 要实现的四块

1购买接口 jiu POST /api/v1/license/purchase

鉴权 = jiu 自己的登录态(当前用户/门店)。步骤:

  1. 校验登录态,拿到当前 shop_id;入参为套餐(前端传 biz_code 或 plan 标识)。
  2. Purchase 记录(status=pending,存 shop_id / product_biz_code / amount)。
  3. 调 pay 下单(见 §5.1),biz_ref = purchase_idreturn_url = jiu 结果页。
  4. 把 pay 返回的 pay_url(和 out_trade_no,回写 Purchase)返给客户端。
建议新增表 license_purchasesid · shop_id · product_biz_code · amount · out_trade_no(index) · status(pending/paid/failed) · paid_at · created_atout_trade_no 兼作对账键与幂等键。

2webhook 接收器 jiu POST /api/v1/pay/callback(核心)

  1. 读原始 body(勿被框架提前解析掉),取 4 个签名头。
  2. 验签hmac.Equal(hmacSign(secret,"jiu",ts,nonce,rawBody), X-Pay-Sign)校验时间戳 ±5 分钟。任一不过 → 401,不处理。
  3. 幂等:按 out_trade_no 查 Purchase;若已 paid → 直接回 {"code":"SUCCESS"}(pay 会重发,必须幂等)。
  4. 金额校验:payload.amount 与 Purchase.amount 一致(分级比较,防篡改)。
  5. 续期:按 product_biz_code 映射权益(§6),给该 shop 的 License 直接叠加 ExpiresAt(§7)。
  6. 标记 Purchase=paid;回 HTTP 200 + {"code":"SUCCESS"}(pay 认响应体含 SUCCESS 才算成功,否则退避重试)。
这是「钱已到账」的入口——验签 + 幂等 + 金额校验三者缺一不可。验签失败绝不能续期(防伪造「已付款」骗授权)。

3续期逻辑 jiu

复用/参照现有 license 服务(见 授权体系设计)。方式 = 直接叠加(不生成兑换码):

base := license.ExpiresAt
if base == nil || base.Before(now) { base = now }   // 已过期从现在起算
license.ExpiresAt = base + duration(bizCode)         // +30 或 +365 天
license.Tier       = tier(bizCode)                   // standard / pro
license.Type       = billType(bizCode)               // monthly / annual
license.MaxDevices = maxDevices(bizCode)             // 2 / 5
license.Features    = features(bizCode)              // 仓库数/图片额度/AI 分析
license.IsActive   = true

4查单兜底 jiu

防 webhook 全丢:对 pending 超时(如 >5 分钟)的 Purchase,定时主动查 pay GET /api/v1/orders/{out_trade_no},若返回 status=paid 则走与 webhook 相同的续期入账(同样幂等)。

5. 与 pay 的接口契约

5.1 下单 pay POST https://pay.51yanmei.com/api/v1/orders

// Headers: Content-Type: application/json + 4 个签名头(§3)
// Body(对这份原始 body 签名):
{
  "product_id": 3,                       // pay 套餐 id(见 §6,或先 GET /products 按 biz_code 查 id)
  "biz_system": "jiu",
  "biz_ref": "<purchase_id>",           // jiu 的购买记录 id,回调原样带回
  "return_url": "https://jiu.51yanmei.com/license/result"
}
// 成功响应:
{ "data": { "pay_url": "https://openapi.alipay.com/...", "out_trade_no": "yanmei-...", "amount": "2999.00", "subject": "年付标准" } }

data.pay_url 交给客户端打开;out_trade_no 回写 Purchase。金额以 pay 套餐表为准,不要自己传金额。

5.2 回调 payjiu(pay 主动 POST 到你的接收器)

// Headers: 4 个签名头(§3),你要验签
{
  "out_trade_no": "yanmei-20260703...-xxxx",
  "biz_system": "jiu",
  "biz_ref": "<purchase_id>",
  "product_biz_code": "annual_standard",
  "amount": "2999.00",
  "trade_no": "2026070322001...",        // 支付宝交易号
  "channel": "alipay",
  "paid_at": "2026-07-03T14:36:30+08:00"
}
// 你必须回:HTTP 200 + {"code":"SUCCESS"}(否则 pay 每 60s 重试,24h 内)

6. 套餐 biz_code → 权益映射

套餐biz_code价格时长tier客户端(MaxDevices)权益(Features)
月付·标准monthly_standard¥299+30 天standard2单店/单仓库 · 千张图片分享
年付·标准annual_standard¥2999+365 天standard2单店/单仓库 · 千张图片分享
月付·高级monthly_pro¥599+30 天pro5单店/多仓库 · 万张照片分享 · 免费 AI 周/月度商业数据分析
年付·高级annual_pro¥5999+365 天pro5单店/多仓库 · 万张照片分享 · 免费 AI 周/月度商业数据分析

建议 Features(License.Features JSON)编码:{"max_warehouses": 1|0(0=不限), "image_quota": 1000|10000, "ai_analysis": false|true}。具体键名由 jiu 侧按现有能力开关定义,本表给权益语义。

jiu 当前 Tier 未做能力差异(仅 standard)。标准/高级的实际功能开关(多仓库限制、图片额度、AI 分析)需 jiu 侧后续按此权益表落地;先把 tier/max_devices/features 正确写入 License,能力 gating 逐步接。

7. 安全红线

8. 客户端(Web + App)

9. 联调 Checklist

  1. 约定并配置共享密钥(pay BIZ_JIU_SECRET = jiu 侧同值);pay 侧配 BIZ_JIU_CALLBACK_URL = jiu 接收器地址。
  2. pay 侧 seed 4 个真实套餐(biz_code 见 §6),jiu 记下对应 product_id(或用 GET /products 动态取)。
  3. jiu 实现购买接口 → 下单 → 拿 pay_url(先验证签名被 pay 接受)。
  4. jiu 实现 webhook 接收器 → 用 pay 真实 1 分钱订单触发(临时把某套餐改 0.01)→ 验签/幂等/续期全走通。
  5. 验证幂等(pay 重发不重复续期)、查单兜底(模拟 webhook 丢失)。
  6. 客户端 Web + App webview 两端各跑一遍付款→回跳→授权已续。

相关:授权体系设计(兑换券模型) · 数据库 Schema · pay 侧设计见 pay 仓 docs/pay接入jiu授权续费对接方案.html