授权码开放接口文档

v1.0

授权码开放接口文档

本文面向需要把「支付 → 发码 → 激活 → 校验」做进自己软件的开发者(客户端软件 / 仅推广产品)。文档按接口逐个说明调用时机、请求参数、返回字段、错误码。

一、核心概念

概念 字段 / 术语 说明
软件发码密钥 licenseApiSecret 产品开启「授权激活」时系统自动生成,在开发者中心产品编辑表单可查看。软件服务端用它计算 HMAC 签名,平台用它验签;只能保存在你的软件服务端,严禁写入客户端。
授权码 licenseCode 平台生成的唯一授权码,用户凭它激活软件。
机器码 machineCode 客户端软件生成的设备标识(每台电脑唯一,建议对机器指纹做哈希,至少 8 位)。
激活凭证 activationToken 平台在激活 / 发码时签发的凭证,软件保存后每次启动校验时回传。
订单号 clientOrderId 软件内支付订单号(客户端或服务端生成)。平台按「产品 + 订单号」幂等,防止重复扣费重复发码。

二、接入准备

  1. 发布产品时开启「授权激活」,保存系统生成的软件发码密钥(licenseApiSecret);
  2. 在发布表单配置版本列表(如 BASIC/PRO/ULTIMATE)与升级策略(SAME_CODE 原码升级 / NEW_CODE 换新码);
  3. 将密钥保存在软件服务端,客户端只保留授权码与激活凭证。

三、接口总览

接口前缀:https://www.powersoftware.app/frontApi(本地 / 测试环境按你的部署域名替换)。

接口 调用时机 鉴权 签名
POST /license/software/generate 软件内支付成功后 无(验签) 需要
POST /license/software/upgrade 续费 / 升级版本支付成功后 无(验签) 需要
POST /license/activate 用户首次激活 / 换新设备 无 不需要
POST /license/verify 软件每次启动 无 不需要
POST /license/deactivate 用户解绑旧设备(个人中心) 登录 不需要

四、签名规则(发码 / 升级接口必须)

签名串:按以下固定顺序、以换行符 \n 拼接(缺省字段填空值:edition 空串、expiryDays 填 0):

productId \n machineCode \n edition \n expiryDays \n clientOrderId \n licenseCode \n timestamp

签名:HMAC-SHA256(软件发码密钥 licenseApiSecret, 签名串),结果用 Base64URL 编码后放入 signature 字段。

防重放:timestamp 为毫秒时间戳,平台要求与服务器时间差不超过 5 分钟,超时直接返回 signatureInvalid。

Node.js 示例:

const crypto = require("crypto");
const payload = [productId, machineCode, edition, expiryDays, clientOrderId, licenseCode, timestamp].join("\n");
const signature = crypto.createHmac("sha256", API_SECRET).update(payload).digest("base64url");

Python 示例:

import hashlib, hmac, base64
payload = "\n".join([str(productId), machineCode, edition, str(expiryDays), clientOrderId, licenseCode, str(timestamp)])
signature = base64.urlsafe_b64encode(hmac.new(API_SECRET.encode(), payload.encode(), hashlib.sha256).digest()).decode().rstrip("=")

(Base64URL 即标准 Base64 将 + 换 -、/ 换 _,并去掉末尾 = 填充。)

五、接口详情

5.1 生成授权码(软件内支付后调用)

调用时机:用户在软件内完成支付后,由软件服务端立即调用一次并保存返回的授权码与激活凭证。同一订单只应调用一次;重复调用因幂等不会重复发码。

POST /license/software/generate

请求参数(JSON body):

参数 类型 必填 来源 说明
productId number 是 软件内置 / 服务端配置 产品 ID,与平台发布的产品一致
machineCode string 是 客户端生成 当前设备机器码,≥8 位
edition string 否 客户端支付时选择 版本标识,须在产品版本列表内;缺省取产品默认第一个版本
expiryDays number 否 客户端支付时选择 有效期天数,0=永久;范围 0-3650
clientOrderId string 是 客户端 / 服务端生成 软件内支付订单号,1-64 字符;幂等键
timestamp number 是 软件服务端生成 毫秒时间戳
signature string 是 软件服务端计算 HMAC 签名,≥10 字符

成功响应 content 字段:

字段 类型 说明
licenseId number 授权码记录 ID
licenseCode string 授权码(写入软件授权信息)
productId number 产品 ID
edition string 版本标识
machineId number 已绑定机器记录 ID
activatedAt string 激活时间(ISO8601)
expiryTime string/null 到期时间(ISO8601),null=永久
trialExpiryTime string/null 试用到期时间快照(ISO8601),null=非试用转购买
trialExpiryTime string/null 试用到期时间快照(ISO8601),null=非试用转购买
trialExpiryTime string/null 试用到期时间快照(ISO8601),null=非试用转购买
maxMachines number 最大可绑定机器数
activationToken string 激活凭证,软件保存用于启动校验

响应示例:

{
  "code": "SUCCESS",
  "requestId": "xxx",
  "success": true,
  "content": {
    "licenseId": 1001,
    "licenseCode": "AB12-CD34-EF56-GH78",
    "productId": 88,
    "edition": "PRO",
    "machineId": 2001,
    "activatedAt": "2026-08-05T10:00:00.000Z",
    "expiryTime": "2027-08-05T10:00:00.000Z",
    "trialExpiryTime": "2026-12-01T10:00:00.000Z",
    "trialExpiryTime": "2026-12-01T10:00:00.000Z",
    "trialExpiryTime": "2026-12-01T10:00:00.000Z",
    "maxMachines": 1,
    "activationToken": "eyJhbGciOiJIUzI1NiJ9..."
  }
}

本接口错误码:productIdRequired、machineCodeInvalid、orderIdRequired、productNotFound、productNotEnabled、apiSecretMissing、signatureInvalid、orderAlreadyUsed。

5.2 升级版本 / 续费(支付后调用)

调用时机:用户在软件内完成续费或升级版本支付后调用;同一订单只应调用一次。

POST /license/software/upgrade

请求参数(JSON body):

参数 类型 必填 来源 说明
productId number 是 软件内置 / 服务端配置 产品 ID
licenseCode string 是 软件本地保存 当前授权码,≥10 位
machineCode string 否 客户端生成 当前机器码;换新码策略迁移绑定、补绑机器时传
edition string 是 客户端支付时选择 目标版本标识
expiryDays number 否 客户端支付时选择 新增有效期天数,0=永久;范围 0-3650
clientOrderId string 是 客户端 / 服务端生成 本次订单号;幂等键
timestamp number 是 软件服务端生成 毫秒时间戳
signature string 是 软件服务端计算 HMAC 签名

处理策略(由产品配置决定):

  • SAME_CODE(原码升级):授权码不变,机器绑定不失效,有效期顺延(永久授权保持永久);响应在 5.1 字段基础上增加 upgraded: true;
  • NEW_CODE(换新码):吊销旧码、生成新码并迁移机器绑定;响应额外返回 oldLicenseId,licenseCode 为新码。

成功响应 content 字段:与 5.1 相同,另加 upgraded: true;NEW_CODE 策略下返回 oldLicenseId(旧授权码记录 ID,旧码已被吊销)。

本接口错误码:productIdRequired、licenseCodeRequired、orderIdRequired、productNotFound、productNotEnabled、apiSecretMissing、signatureInvalid、codeNotFound、revoked、orderAlreadyUsed、editionRequired。

5.3 激活授权(用户首次激活 / 换新设备)

调用时机:用户拿到授权码(自助发码、后台发码等场景)后,在客户端软件激活页输入授权码并激活;换新设备时在新设备调用。

POST /license/activate

请求参数(JSON body):

参数 类型 必填 来源 说明
licenseCode string 是 用户输入 授权码,≥10 位
machineCode string 是 客户端生成 当前设备机器码,≥8 位

成功响应 content:同 5.1(licenseId、licenseCode、productId、edition、machineId、activatedAt、expiryTime、maxMachines、activationToken)。

说明:

  • 同一授权码可绑定多台机器,受 maxMachines 限制(默认取产品配置);
  • 同一机器重复激活:复用原绑定并重新签发激活凭证(幂等);
  • 激活失败会按 IP + 授权码计数限流,多次失败返回 tooManyAttempts。

本接口错误码:codeNotFound、revoked、expired、machineLimit、tooManyAttempts。

5.4 校验授权(软件启动时)

调用时机:软件每次启动(或解锁功能前)调用;软件须保存 5.1 / 5.2 / 5.3 返回的 activationToken。

POST /license/verify

请求参数(JSON body):

参数 类型 必填 来源 说明
licenseCode string 是 软件本地保存 授权码,≥10 位
machineCode string 是 客户端生成 当前设备机器码,≥8 位
activationToken string 是 平台签发、软件保存 激活凭证,≥20 位

成功响应 content 字段:

字段 类型 说明
valid boolean 恒为 true(失败走错误码)
licenseId number 授权码记录 ID
productId number 产品 ID
edition string 当前版本标识
expiryTime string/null 到期时间(ISO8601),null=永久
trialExpiryTime string/null 试用到期时间快照(ISO8601),null=非试用转购买
trialExpiryTime string/null 试用到期时间快照(ISO8601),null=非试用转购买
trialExpiryTime string/null 试用到期时间快照(ISO8601),null=非试用转购买

说明:

  • 校验结果有约 60 秒缓存;吊销、解绑、升级等写操作会主动失效缓存,无需担心延迟;
  • 软件应根据 expiryTime 判断是否过期,过期后停止解锁功能。
  • 若授权由试用转购买,可结合 trialExpiryTime 对高阶功能做宽限:未过 trialExpiryTime 前继续放行。
  • 若授权由试用转购买,可结合 trialExpiryTime 对高阶功能做宽限:未过 trialExpiryTime 前继续放行。
  • 若授权由试用转购买,可结合 trialExpiryTime 对高阶功能做宽限:未过 trialExpiryTime 前继续放行。

本接口错误码:tokenInvalid、machineNotActivated、invalidOrRevoked、expired、tooManyAttempts。

5.5 解绑设备(换机前,需登录)

调用时机:用户在「个人中心 → 我的授权」对旧设备执行解绑,然后在新设备调用 5.3 激活。

POST /license/deactivate

请求参数(JSON body):

参数 类型 必填 来源 说明
licenseCode string 是 用户授权列表 授权码,≥10 位
machineCode string 是 用户授权列表 要解绑的机器码,≥8 位

成功响应 content:true。

说明:

  • 仅授权码归属用户(购买用户或绑定机器码登记用户)可操作;
  • 解绑配额:默认 30 天内仅可解绑 1 次(运营后台可配置),超限返回 unbindQuotaExceeded。

本接口错误码:noPermission、machineNotBound、unbindQuotaExceeded。

六、错误码汇总

业务错误统一返回(tip 为可读错误信息):

{
  "code": "BUSINESS_WARNING",
  "requestId": "xxx",
  "success": false,
  "errorMessage": "LICENSE_GENERATE_FAILED",
  "tip": "签名校验失败"
}

参数校验失败返回 code: "PARAM_VALIDATE_FAILED",tip 为具体字段提示。

错误码 含义
productNotEnabled 产品未启用授权激活
apiSecretMissing 产品未配置软件发码密钥(licenseApiSecret)
signatureInvalid 签名校验失败(密钥不一致、签名串顺序错误或时间戳超窗)
machineCodeInvalid 机器码无效(长度不足 8 位)
orderAlreadyUsed 该订单已生成过授权码(幂等冲突:换机器码重放)
codeNotFound 授权码不存在
revoked 授权码已被吊销或不可用
expired 授权码已过期
invalidOrRevoked 校验时授权无效或已被吊销
machineNotActivated 该机器未激活
tokenInvalid 激活凭证无效
machineLimit 授权码绑定机器数已达上限
machineNotBound 该机器未绑定
noPermission 无权操作该授权码
unbindQuotaExceeded 30 天内仅可解绑 1 次,请稍后再试
tooManyAttempts 尝试次数过多,请稍后再试
productNotFound 产品不存在
editionRequired 请选择目标版本

七、典型流程

  1. 软件内支付闭环:支付成功 → software/generate(返回授权码 + 激活凭证)→ 软件保存 → 每次启动 verify → 解锁功能;续费 / 升级 → software/upgrade;
  2. 自助发码 / 后台发码:开发者中心或平台后台生成授权码 → 用户获得授权码 → 客户端 activate 绑定设备并获取激活凭证 → 启动 verify;
  3. 换机:个人中心 deactivate 解绑旧设备 → 新设备 activate → verify;
  4. 升级策略差异:SAME_CODE 原码升级(码不变);NEW_CODE 换新码(旧码吊销、新码自动迁移机器绑定)。

八、注意事项

  • 密钥只存服务端:签名只能在你的软件服务端完成,客户端拿到密钥等于授权体系失效;
  • 机器码稳定性:建议使用稳定的机器指纹并做哈希;重装系统导致机器码变化时需先解绑再激活;
  • 幂等:clientOrderId 请保证全局唯一(如 时间戳 + 随机数),重复请求不会重复发码;
  • 缓存:verify 结果缓存约 60 秒,写操作即时失效;
  • 限流:激活 / 校验失败会限流,客户端应做好错误提示与重试间隔;
  • 以平台返回为准:有效期、版本、机器数上限等以平台判定为准,软件端不要自行放宽。

六、先用后付(试用领取 / 购买页跳转)

客户端软件「先用后付」产品:用户 0 元下载后由客户端调用 POST /license/trial/claim 领取试用授权。

请求参数:

参数 类型 必填 说明
productUniqueCode string 是 产品唯一编码(须为先用后付产品)
machineCode string 是 机器码(≥8 位,建议用 SDK 的跨语言机器码算法)

成功响应与 5.1 相同(licenseCode / activationToken / expiryTime = 领取时间 + 试用天数)。同时返回 trialExpiryTime(试用到期快照,与 expiryTime 同值)。同时返回 trialExpiryTime(试用到期快照,与 expiryTime 同值)。同时返回 trialExpiryTime(试用到期快照,与 expiryTime 同值)。 同产品 + 机器码重复调用返回同一试用授权(幂等),不重复发放。

试用到期后付费功能需购买授权版本:客户端点击付费功能校验失败时,弹窗提示并携带机器码跳转购买页:

https://www.powersoftware.app/product/license/purchase?productUniqueCode={productUniqueCode}&machineCode={machineCode}

支付成功后平台发放正式授权码(source=PLATFORM),购买页展示授权码并同步发送邮件;SDK 参考 ps-help/v3/doc/授权SDK规范_v3.md。

七、官方 SDK(Node / Python / Java)

三语言官方 SDK(机器码跨语言一致、HMAC 签名、60s 校验缓存、购买页跳转)与中英文规范见 GitHub:

https://github.com/mizhanchengxi/powersoftware-license-sdk

八、升级策略标识(licenseUpgradeMode)

POST /license/activate、POST /license/verify、POST /license/trial/claim 三个接口的成功响应 content 中附带返回产品级升级策略配置:

字段 类型 说明
licenseUpgradeMode string SAME_CODE=原码不变(升级/续费后授权码不变);NEW_CODE=原码换绑(升级/续费吊销旧码、签发新码)

客户端用途:

  • 据此决定是否展示「绑定授权码」输入框:SAME_CODE 下授权码始终不变,无需引导用户重新输入授权码;NEW_CODE 下升级/续费会签发新码,须以接口返回的新 licenseCode 覆盖本地保存的授权码;
  • verify 每次启动返回最新配置,客户端可持续同步。

说明:

  • 该字段为产品级配置(开发者在发布表单配置升级策略),未配置时按 SAME_CODE 处理;
  • verify 结果缓存约 60 秒,平台修改配置后最长 60 秒生效;
  • software/upgrade 已按策略实际返回原码或新码,无需依赖该字段。

九、试用到期时间快照(trialExpiryTime)

POST /license/activate、POST /license/verify、POST /license/trial/claim 三个接口的成功响应 content 中附带返回试用到期时间快照:

字段 类型 说明
trialExpiryTime string | null 原试用授权的到期时间(ISO 8601);null = 非试用转购买(含纯首购、开发者发码、试用未转购买)

产生规则:

  • 领取试用时写入,与试用授权的 expiryTime 同值;
  • 试用后购买(原码升级或换新码)时,平台先查试用码原到期时间并固化到本字段;购买后 expiryTime 变为所购版本的有效期,本字段保持不变;
  • 过期试用转购买同样固化(值为过去时刻),客户端可据此识别「无宽限」;
  • 后续付费版本升级/续费(原码或换新码)保留该字段,链路不丢失。

客户端用途(建议):先用后付产品试用期内全功能可用,购买较低版本后高档功能会因版本门控立即锁定。客户端可读取 trialExpiryTime 自行决定过渡策略,例如:所购版本不足以解锁某高档功能、且当前时间早于 trialExpiryTime 时,对该功能保留宽限放行,到达 trialExpiryTime 后再锁定并引导补差价升级。是否使用、宽限方式由客户端自行决定,平台不做强制。

另:领取试用时若该机器在该产品下已存在非试用授权(试用转购买/首购/开发者发码等,即已购买),接口返回错误码 trialAlreadyPurchased,平台不再新增试用授权码;已吊销(退款)的授权不算已购。


附录:用 AI Agent 一键上架授权产品

反复在开发者中心上架多个授权产品?我们把整条链路(注册 → 申请合作伙伴(含人工审核闸门)→ 上传素材 → 提交产品)沉淀成了一份公开的 Agent Skill,Qoder / Claude Code / 兼容 Agent 一行命令即可安装使用:

  • 仓库:https://github.com/mizhanchengxi/powersoftware-agent-skills
  • Claude Code 一键安装:/plugin marketplace add mizhanchengxi/powersoftware-agent-skills
  • 详细指南:帮助中心 → 开发者指南 → 《用 AI Agent 一键上架授权产品》(agreement_code = AGENT_SKILL_PUBLISH_GUIDE)

Skill 严格遵循本文档描述的所有约束(businessType 枚举、licenseEditions 价格升序、素材上传三步顺序、DEVELOPER 角色只在登录时注入 等),并不绕过任何平台审核闸门。