| 项 | 值 |
|---|---|
| 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 侧自定,同值) |
签名串(4 段用换行 \n 连接,再 HMAC-SHA256,最后 base64):
sign = base64( HMAC_SHA256( secret, system + "\n" + timestamp + "\n" + nonce + "\n" + rawBody ) )
请求头:
| Header | 说明 |
|---|---|
| X-Pay-System | 固定 jiu |
| X-Pay-Timestamp | Unix 秒(服务端校验 ±5 分钟窗口) |
| X-Pay-Nonce | 随机串(如 uuid) |
| X-Pay-Sign | 上面算出的签名 |
rawBody = HTTP 请求体的原始字节(验签/签名都对同一份原始 body,勿先反序列化再重拼)。
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)
POST /api/v1/license/purchase鉴权 = jiu 自己的登录态(当前用户/门店)。步骤:
shop_id;入参为套餐(前端传 biz_code 或 plan 标识)。Purchase 记录(status=pending,存 shop_id / product_biz_code / amount)。biz_ref = purchase_id,return_url = jiu 结果页。pay_url(和 out_trade_no,回写 Purchase)返给客户端。license_purchases:id · shop_id · product_biz_code · amount · out_trade_no(index) · status(pending/paid/failed) · paid_at · created_at。out_trade_no 兼作对账键与幂等键。POST /api/v1/pay/callback(核心)hmac.Equal(hmacSign(secret,"jiu",ts,nonce,rawBody), X-Pay-Sign);校验时间戳 ±5 分钟。任一不过 → 401,不处理。out_trade_no 查 Purchase;若已 paid → 直接回 {"code":"SUCCESS"}(pay 会重发,必须幂等)。product_biz_code 映射权益(§6),给该 shop 的 License 直接叠加 ExpiresAt(§7)。{"code":"SUCCESS"}(pay 认响应体含 SUCCESS 才算成功,否则退避重试)。复用/参照现有 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
防 webhook 全丢:对 pending 超时(如 >5 分钟)的 Purchase,定时主动查 pay GET /api/v1/orders/{out_trade_no},若返回 status=paid 则走与 webhook 相同的续期入账(同样幂等)。
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 套餐表为准,不要自己传金额。
// 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 内)
| 套餐 | biz_code | 价格 | 时长 | tier | 客户端(MaxDevices) | 权益(Features) |
|---|---|---|---|---|---|---|
| 月付·标准 | monthly_standard | ¥299 | +30 天 | standard | 2 | 单店/单仓库 · 千张图片分享 |
| 年付·标准 | annual_standard | ¥2999 | +365 天 | standard | 2 | 单店/单仓库 · 千张图片分享 |
| 月付·高级 | monthly_pro | ¥599 | +30 天 | pro | 5 | 单店/多仓库 · 万张照片分享 · 免费 AI 周/月度商业数据分析 |
| 年付·高级 | annual_pro | ¥5999 | +365 天 | pro | 5 | 单店/多仓库 · 万张照片分享 · 免费 AI 周/月度商业数据分析 |
建议 Features(License.Features JSON)编码:{"max_warehouses": 1|0(0=不限), "image_quota": 1000|10000, "ai_analysis": false|true}。具体键名由 jiu 侧按现有能力开关定义,本表给权益语义。
window.location 跳转;付完支付宝跳回 return_url(带 out_trade_no),结果页轮询 jiu 授权状态。wap.pay(H5)自动拉起支付宝 App;webview 需放行 alipays:// / alipay:// scheme 唤起(拦截非 http(s) 跳转交系统打开),付完靠 return_url 回跳 webview 页面。BIZ_JIU_SECRET = jiu 侧同值);pay 侧配 BIZ_JIU_CALLBACK_URL = jiu 接收器地址。相关:授权体系设计(兑换券模型) · 数据库 Schema · pay 侧设计见 pay 仓 docs/pay接入jiu授权续费对接方案.html