授权码License 开发调试上线全流程指南

v1.0.0

客户端软件开发 → 调试 → 上线全流程指南(v4)

适用于:在幂栈网发布客户端软件并接入授权码体系的开发者。 本文覆盖从「创建产品」到「真实用户购买」的完整生命周期,重点是 v4 新增的双环境调试能力:上线前用真实支付链路(Waffo Test 环境)把自己的电脑当调试机走通全流程。

一、全流程总览

┌──────────────┐   ┌──────────────┐   ┌──────────────┐   ┌──────────────┐
│ ① 开发阶段   │ → │ ② 调试阶段   │ → │ ③ 上线阶段   │ → │ ④ 上线后运营 │
│              │   │              │   │              │   │              │
│ 创建产品草稿 │   │ 注册调试机   │   │ 提交审核     │   │ 查看订单     │
│ 集成授权 SDK │   │ 走通真实链路 │   │ 审核通过     │   │ 解绑/退款    │
│ 保存草稿     │   │ (Test 环境)│   │ 同步生产环境 │   │ 版本迭代     │
└──────────────┘   └──────────────┘   └──────────────┘   └──────────────┘
阶段 产品状态 授权/订单落在哪 会产生真实收入吗
① 开发 草稿(DRAFT)
② 调试 草稿 / 审核中 Test 表(授权码带 T- 前缀) 否(测试支付通道)
③ 上线 已发布 生产表

核心规则:调试与生产完全隔离。 调试机上发生的一切(试用、下单、支付、发码、激活、退款)只影响测试表,不进分润、不进对账、不影响任何真实用户;非调试机的访问永远走生产链路。

二、开发阶段

2.1 创建产品

  1. 登录幂栈网开发者中心 → 发布产品,产品形态选「客户端软件」
  2. 售卖方式选 先用后付,填写试用天数(建议 7~14 天)
  3. 配置授权版本(edition):默认三档 BASIC / PRO / ULTIMATE,可自定义 code、名称、价格与功能清单
  4. 勾选「平台代收授权费」(先用后付强制勾选)
  5. 上传软件包与介绍素材

2.2 保存草稿(新增能力)

产品表单底部有两个按钮:

按钮 行为
保存草稿 产品存为 DRAFT 状态,不进审核队列,可随时继续编辑
保存并提交审核 产品进入审核队列(PENDING_RELEASE

草稿/提审保存后,平台会异步把商品同步到 Waffo Test 环境(失败仅告警不阻塞保存),这是调试阶段能下单的前提。因此建议:开发期先保存草稿即可开始调试,无需提审

保存后记下 productUniqueCode(发布页可见,非密钥)。

2.3 集成授权 SDK(两种接入场景)

幂栈网为客户端软件提供两种授权接入方式,开发者根据自身情况选择:

场景 A:平台完整链路(先用后付) 场景 B:订单不在平台
适用产品 客户端软件 + 先用后付售卖方式 仅推广软件(自行收款);或服务端软件 / 客户端软件,先付后用(PAY_FIRST)售卖方式或 VIP 功能的自行收款
谁管收款 幂栈网平台(Waffo、支付宝、PayPal);调试环节使用 Waffo 测试环境作为支付通道 开发者自己(软件内支付或其他渠道)
谁管发码 平台支付成功后自动发码 软件调平台 API 发码(HMAC 签名)
需要授权密钥 licenseApiSecret ❌ 不需要 ✅ 需要(仅存服务端,不可存客户端)
需要服务端? ❌ 不需要,纯客户端即可 ✅ 需要(保管 licenseApiSecret,转发发码请求)
试用授权 ✅ 支持(claimTrial ❌ 无试用(试用仅支持先用后付产品)
购买页 平台提供,SDK 一行跳转 无平台购买页,开发者自行处理

关于版本配置:所有产品形态(客户端/服务端/仅推广)均可启用授权码并自定义版本(BASIC/PRO/ULTIMATE 等)。版本价格和功能清单仅在开发者勾选「平台代收授权费」(licensePlatformPayment)时展示在购买页;不勾选则开发者自行处理收款,平台仅提供授权码发放与校验能力。

如何选择

  • 你是独立开发者 / 小团队,没有自己的服务端 → 选 场景 A(本文第四、五章的流程即以场景 A 为主线)
  • 你已有支付渠道(如微信/支付宝商户),只想用平台发码校验 → 选 场景 B

2.3.1 SDK 安装

三语言(Node.js / Python / Java)均为零依赖,直接拷贝源码到你的项目中,无需任何包管理器。机器码算法跨语言一致(同一台机器生成相同 machineCode)。SDK 仓库:github.com/mizhanchengxi/powersoftware-license-sdk

2.3.2 场景 A:平台完整链路(先用后付)

前置条件:产品售卖方式为先用后付、已配置试用天数与授权版本、勾选「平台代收授权费」。不需要授权密钥 licenseApiSecret

# Python
from ps_license_sdk import LicenseClient, machine_code

client = LicenseClient(product_unique_code="PRO-2026-001", api_secret="")  # api_secret 留空
mc = machine_code()
// Node.js
import { LicenseClient, machineCode } from './index.js';

const client = new LicenseClient({ productUniqueCode: 'PRO-2026-001', apiSecret: "" });  // 场景 A 密钥留空
const mc = machineCode();
// Java
import com.powersoftware.sdk.LicenseClient;

LicenseClient client = new LicenseClient("PRO-2026-001", "");  // 场景 A 密钥留空
String mc = LicenseClient.machineCode();

完整接入流程:

首次启动
    │
    ├─ claimTrial(machineCode) ────────────→ 获取试用授权
    │     ↓ 返回 { licenseCode, activationToken, licenseUpgradeMode }
    │     ↓ 本地持久化存储
    │
    ├─ 试用期内:所有功能可用
    │     │
    │     └─ 点击付费功能 → verifyCached() 60s 缓存校验 → 放行
    │
    └─ 试用到期 / 未激活
          │
          ├─ verifyCached() 返回 invalid/expired
          ├─ 弹窗提示"需要购买激活授权"
          └─ purchaseUrl(machineCode) → 跳转平台购买页
                │
                ↓ 用户在平台支付 → 平台发码 + 邮件送达
                │
          用户回到软件,输入授权码
                │
                ├─ activate(licenseCode, machineCode)
                │     ↓ 返回 { activationToken, licenseUpgradeMode },本地持久化存储
                │
                └─ 后续使用 → verifyCached() 校验 → 放行

需实现:claimTrial(首次启动领取试用)→ verifyCached(付费功能校验)→ purchaseUrl(未授权跳购买页)→ activate(用户输入授权码激活)→ 本地持久化 licenseCode + activationTokenLicenseError 错误码处理。

核心代码(Python):

import json
from pathlib import Path
from ps_license_sdk import LicenseClient, machine_code

# ---------- 初始化 ----------
client = LicenseClient(product_unique_code="PRO-2026-001", api_secret="")
CRED_FILE = Path.home() / ".myapp" / "license.json"

def load_cred():
    if CRED_FILE.exists():
        return json.loads(CRED_FILE.read_text())
    return {}

def save_cred(d):
    CRED_FILE.parent.mkdir(parents=True, exist_ok=True)
    CRED_FILE.write_text(json.dumps(d, ensure_ascii=False))

# ---------- 首次启动:领取试用 ----------
def claim_trial():
    mc = machine_code()
    try:
        result = client.claim_trial(mc)
        save_cred({
            "licenseCode": result["licenseCode"],
            "activationToken": result["activationToken"],
        })
        print(f"试用已激活,授权码:{result['licenseCode']}")
    except Exception as e:
        print(f"领取试用失败:{e}")

# ---------- 校验授权(点击付费功能时调用) ----------
def check_license(required_edition="PRO"):
    cred = load_cred()
    if not cred.get("licenseCode"):
        return {"valid": False, "reason": "未激活"}

    mc = machine_code()
    try:
        result = client.verify_cached(
            cred["licenseCode"], mc, cred["activationToken"]
        )
        if not result.get("valid"):
            return {"valid": False, "reason": "授权无效或已过期"}

        user_edition = result.get("edition", "")
        levels = {"BASIC": 0, "PRO": 1, "ULTIMATE": 2}
        if levels.get(user_edition, 0) < levels.get(required_edition, 0):
            return {"valid": False, "reason": f"需要 {required_edition} 及以上版本"}

        return {"valid": True, "edition": user_edition,
                "expiryTime": result.get("expiryTime"),
                "trialExpiryTime": result.get("trialExpiryTime")}
    except Exception as e:
        return {"valid": False, "reason": str(e)}

# ---------- 激活授权码(用户购买后输入) ----------
def activate(license_code):
    mc = machine_code()
    try:
        result = client.activate(license_code, mc)
        save_cred({
            "licenseCode": license_code,
            "activationToken": result["activationToken"],
        })
        return True
    except Exception as e:
        print(f"激活失败:{e}")
        return False

# ---------- 跳转购买页 ----------
def open_purchase_page():
    mc = machine_code()
    url = client.purchase_url(mc)
    import webbrowser
    webbrowser.open(url)

功能拦截示例:

# 定义功能版本要求
FEATURE_EDITION = {
    "basic_feature":   "BASIC",
    "plus_feature":    "PRO",
    "ultimate_feature": "ULTIMATE",
}

def run_feature(feature_name):
    required = FEATURE_EDITION.get(feature_name, "BASIC")
    if required == "BASIC":
        do_basic_feature()
        return

    result = check_license(required)
    if result["valid"]:
        do_paid_feature(feature_name)
    else:
        print(f"无法使用此功能:{result['reason']}")
        open_purchase_page()

2.3.3 场景 B:订单不在平台(自行收款)

适用产品:仅推广软件(自行收款),或服务端软件 / 客户端软件中先付后用(PAY_FIRST)售卖方式、VIP 功能的自行收款。

前置条件:产品开启 licenseEnabled;在开发者后台获取 licenseApiSecret仅存服务端,不可泄露到客户端)。架构:

客户端                 你的服务端                       幂栈网平台
  │                       │                                 │
  │ 用户付费(你的支付)  │                                 │
  ├──────────────────────→│                                 │
  │                       │ generateForSoftware(            │
  │                       │   machineCode, edition,         │
  │                       │   clientOrderId)                │
  │                       │ (HMAC 签名 + timestamp)         │
  │                       ├────────────────────────────────→│
  │                       │ ← 返回 licenseCode              │
  │ ← 返回 licenseCode    │                                 │
  │                       │                                 │
  │ activate(licenseCode, machineCode)                     │
  ├───────────────────────────────────────────────────────→│
  │ ← 返回 activationToken                                 │
  │                       │                                 │
  │ verifyCached(...)     │                                 │
  ├───────────────────────────────────────────────────────→│
  │ ← { valid, edition, expiryTime }                       │

服务端(保管授权密钥 licenseApiSecret,发码):

from ps_license_sdk import LicenseClient, machine_code

server_client = LicenseClient(
    product_unique_code="PRO-2026-001",
    api_secret="你的发码密钥_FROM_DEVELOPER_CONSOLE",
)

def issue_license(user_machine_code, edition="PRO", order_id=""):
    """用户付款后,服务端调平台发码"""
    result = server_client.generate_for_software(
        machine_code_value=user_machine_code,
        edition=edition,
        expiry_days=365,
        client_order_id=order_id,
    )
    return result["licenseCode"]

客户端(不需要 licenseApiSecret,激活与校验):

client = LicenseClient(product_unique_code="PRO-2026-001", api_secret="")

def activate(license_code):
    mc = machine_code()
    result = client.activate(license_code, mc)
    save_cred({"licenseCode": license_code,
               "activationToken": result["activationToken"]})

需实现:服务端 generateForSoftware(发码)+ upgradeForSoftware(升级/续费);客户端 activate / verifyCached + 本地凭证持久化 + LicenseError 错误码处理。

2.3.4 SDK 方法速查

方法 场景 A 场景 B 签名 说明
machine_code() 生成机器码(跨语言一致)
claim_trial(mc) 领取试用授权(仅先用后付产品)
activate(code, mc) 激活授权码,绑定机器
verify(code, mc, token) 校验授权状态
verify_cached(code, mc, token) 带本地缓存(60s)的校验
deactivate(code, mc) 解绑机器(需登录,浏览器场景)
purchase_url(mc) 生成平台购买页 URL
generate_for_software(mc, edition, ...) HMAC 软件内发码(仅服务端)
upgrade_for_software(code, edition, ...) HMAC 软件内升级/续费(仅服务端)

返回字段说明activate / verify / claimTrial 三个接口的成功响应均附带 licenseUpgradeMode(产品升级策略:SAME_CODE 原码不变 / NEW_CODE 原码换绑)。客户端据此决定是否展示「绑定授权码」输入框:SAME_CODE 下授权码始终不变,无需引导用户重新输入;NEW_CODE 下升级/续费会签发新码,须以接口返回的新 licenseCode 覆盖本地保存的授权码。产品未配置时按 SAME_CODE 处理;verify 结果缓存约 60 秒,配置变更最长 60 秒生效。同时,成功响应还附带 trialExpiryTime(试用到期时间快照,ISO 8601 字符串;授权非试用转购买时为 null)。客户端可据此实现宽限:用户先试用全功能、再购买较低版本时,高阶功能可继续放行至 trialExpiryTime。同时,成功响应还附带 trialExpiryTime(试用到期时间快照,ISO 8601 字符串;授权非试用转购买时为 null)。客户端可据此实现宽限:用户先试用全功能、再购买较低版本时,高阶功能可继续放行至 trialExpiryTime。同时,成功响应还附带 trialExpiryTime(试用到期时间快照,ISO 8601 字符串;授权非试用转购买时为 null)。客户端可据此实现宽限:用户先试用全功能、再购买较低版本时,高阶功能可继续放行至 trialExpiryTime

双环境调试对两种场景完全透明

  • 场景 A:调试机 claimTrial / 平台购买页支付全链路自动走 Test 环境(见第四章)
  • 场景 B:调试机调用 generateForSoftware 时平台同样按机器码路由到 Test 环境,返回 T- 前缀授权码;服务端代码无需任何改动
  • SDK 调用方式在调试期和上线后完全一致,无需任何环境参数或代码分支,环境路由由平台服务端自动完成

三、授权集成细节(两种场景通用)

3.1 本地凭证存储规范

SDK 本身不负责持久化,由开发者自行实现。存储内容:

{
  "licenseCode": "XXXXXXXXXXXX",
  "activationToken": "YYYYYYYYYYYY",
  "lastVerify": {
    "valid": true,
    "edition": "ULTIMATE",
    "expiryTime": 1735689600000,
    "trialExpiryTime": 1735000000000,
    "trialExpiryTime": 1735000000000,
    "trialExpiryTime": 1735000000000,
    "cachedAt": 1735689600000
  }
}

原则:

  • 只存 licenseCode + activationToken + 最近一次 verify 结果
  • 不存可解密的完整授权信息(防逆向无意义,只作缓存)
  • verifyCached 的 60s 缓存在 SDK 进程内,重启后失效,需重新调 verify

3.2 版本层级对比(edition)

开发者自定义 edition code(如 BASIC / PRO / ULTIMATE),在平台发布页配置。所有产品形态(客户端/服务端/仅推广)启用授权码后均可自定义版本。客户端校验时按层级比较:

EDITION_LEVEL = {"BASIC": 0, "PRO": 1, "ULTIMATE": 2, "TRIAL": 99}

def edition_sufficient(user_edition, required_edition):
    return EDITION_LEVEL.get(user_edition, 0) >= EDITION_LEVEL.get(required_edition, 0)

常见映射(参考):

功能档位 edition code 层级 典型功能
基础版 BASIC 0 基础修图、格式转换
高阶版 PRO 1 批量处理、高清放大
旗舰版 ULTIMATE 2 AI 修复、封面助手

版本名称和 code 由开发者在平台自定义,不强制叫 BASIC/PRO/ULTIMATE。版本价格和功能清单仅在勾选「平台代收授权费」时展示在购买页。

3.3 错误码处理(LicenseError)

SDK 抛出 LicenseError,携带 error_code 属性:

from ps_license_sdk import LicenseError

try:
    result = client.verify_cached(...)
except LicenseError as e:
    if e.error_code == "expired":
        open_purchase_page()
    elif e.error_code == "revoked":
        show_message("授权已被吊销,请联系客服")
    elif e.error_code == "machineLimit":
        show_message("已达机器绑定上限,请在个人中心解绑旧设备")
    elif e.error_code == "NETWORK_ERROR":
        show_message("网络异常,请检查网络后重试")
    else:
        show_message(f"校验失败:{e}")
错误码 含义 客户端建议处理
codeNotFound 授权码不存在 检查输入
revoked 已吊销 提示联系客服
expired 已过期 引导购买/续费
machineLimit 机器绑定数已满 引导去个人中心解绑
tooManyAttempts 触发限流 提示稍后重试
trialNotEnabled 产品未开启试用 检查平台配置
trialAlreadyPurchased 已购买过该产品,无需再领取试用 提示已购并引导至购买页
trialAlreadyPurchased 已购买过该产品,无需再领取试用 提示已购并引导至购买页
trialAlreadyPurchased 已购买过该产品,无需再领取试用 提示已购并引导至购买页
NETWORK_ERROR 网络/超时 离线宽限或提示重试

3.4 购买页跳转(仅场景 A)

3.4.1 购买页 URL

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

SDK 方法:

url = client.purchase_url(mc)

调试机访问该页面时,顶部会出现「调试模式(测试环境)」徽标,支付走测试通道(见第四章)。 语言由购买页按用户浏览器自动检测(URL 前缀 / Accept-Language),SDK 无需关心。

3.4.2 站点:统一用国际站 .app,开发者无需选择购买站

powersoftware.app(国际站) powersoftware.cn(国内站)
支付方式 Waffo(卡 / Apple Pay / Google Pay 等)+ PayPal + 支付宝 仅支付宝
国家/币种 Cloudflare 按 IP 自动识别(CN→CNY,其余→USD) 固定 country=CN,人民币
语言 按 URL 前缀 / Accept-Language 自动检测 固定 zh-CN
定位 开发者唯一需要使用的购买入口 国际站支付宝支付的承接站

开发者无需做购买站选择purchaseUrl 统一指向国际站 .app。海外用户在 .app 直接完成 Waffo / PayPal 支付;国内用户在 .app 选择支付宝时,平台会自动跳转到国内站 .cn 完成支付宝付款(登录状态自动同步,无需重新登录),支付成功后回到授权流程。整条链路对用户和开发者都是透明的。

# 正确:固定使用国际站,不要按地区/语言切换 base
url = client.purchase_url(mc)

# 不建议:自行判断地区传 .cn base——支付宝跳转已由平台处理,
# 且硬编码 .cn 会丢失 Waffo / PayPal 支付方式

四、调试阶段(双环境,v4 新增)

上线前最稳妥的验收方式:把自己日常开发用的电脑登记为「调试设备」,用真实支付链路走一遍完整流程。

4.1 登记机器码

  1. 个人中心 →「我的机器码」→ 登记调试电脑的机器码
    • 机器码可用 SDK 的 machine_code() 生成(同一台机器三语言结果一致)
  2. 记下该机器码

4.2 注册调试设备

  1. 开发者中心 → 产品列表 → 目标产品的「调试设备」按钮
  2. 在弹窗中从已登记机器码中选择,添加到本产品(可写备注)
  3. 限制:每个产品最多 3 台调试设备;支持开关(临时停用路由)与移除

4.3 调试机上会发生什么

已注册且启用中的调试机上,该产品的全链路自动切换到 Waffo Test 环境:

调试机(你的电脑)      幂栈网平台(服务端自动判定环境)
    │                  │
    │ claimTrial(machineCode)
    ├─────────────────→│ 该机器是本产品调试机 → 走 Test 路径
    │                  │
    │ ← 试用授权(T- 前缀授权码,写测试授权表)
    │                  │
    │ purchaseUrl(machineCode)
    ├─────────────────→│ 购买页显示「调试模式(测试环境)」徽标
    │                  │ → 用 Waffo Test 测试卡支付(不扣真钱)
    │                  │ → 订单落测试订单表,平台发测试码(T- 码)
    │ ← 平台发码(T- 码)
    │                  │
    │ activate(T-码, machineCode)
    ├─────────────────→│ 按 T- 前缀直接定位测试表
    │ ← activationToken│
    │                  │
    │ verifyCached(...)
    ├─────────────────→│ 校验通过
    │ ← { valid, edition, expiryTime }

要点:

  • 识别方式:授权码带 T- 前缀即测试授权;购买页顶部会出现「调试模式(测试环境)」徽标
  • 支付:走 Waffo Test 收银台,使用测试卡(如 4576 ... 0110)完成支付,不发生真实扣款
  • 零代码改动:客户端不需要任何改动,同一份代码在调试机和真实用户机器上行为一致(只是后端落表不同)

4.4 建议的调试清单

  • 调试机首次启动 → claimTrial 成功,拿到 T- 前缀试用码
  • 试用期内 verifyCached 返回 valid,付费功能放行
  • purchaseUrl 打开购买页,确认出现「调试模式」徽标
  • 测试卡完成支付 → 收到测试授权码(邮件/页面)
  • activate 激活成功 → verifyCached 通过
  • 版本拦截正确(低版本码访问高版本功能被拦截)
  • 机器绑定上限(machineLimit)与解绑路径正常
  • (仅场景 B) 调试机调 generateForSoftware 返回 T- 前缀授权码,激活校验正常;同一接口换未登记机器调用返回正式码
  • 换一台未登记的机器重复 claimTrial,确认走生产链路(对照验证)

4.5 调试注意事项

事项 说明
测试数据清理 测试授权/订单不影响生产,无需清理;如需重置可在调试设备面板移除后重新添加
调试开关 临时不想走测试链路时,在调试设备面板关闭开关即可,无需删设备
仅推广产品 产品形态为「仅推广」时不同步 Waffo 商品目录,无调试购买链路
无有效价格 主/副价格与所有授权版本均无正价时,测试商品同步会失败(钉钉告警),请至少配置一个版本价格

五、上线阶段

5.1 提交审核

  1. 调试清单全部通过后,在产品表单点「保存并提交审核」
  2. 产品进入审核队列(PENDING_RELEASE

调试期保存过草稿的产品,提审时内容即为最新草稿内容;审核期间再编辑会自动回退为草稿状态(防止审核中内容变更),需重新提审。

5.2 审核通过 → 自动同步生产

平台运营审核通过后:

  1. 产品状态变为已发布、上架
  2. 平台自动把商品同步到 Waffo 生产环境(回写 waffo_product_id),创建/恢复生产收银台商品
  3. 产品详情页、搜索结果对全部用户可见

5.3 真实用户链路(生产)

真实用户(非调试机)的链路与调试完全一致,只是全部落在生产表:

首次启动 → claimTrial 领试用(正式授权码,无 T- 前缀)
        → 试用到期 → purchaseUrl 跳购买页(支付宝 / PayPal 真实支付)
        → 平台支付成功自动发码 + 邮件送达
        → activate 激活 → verifyCached 校验放行

场景 B(自行收款)的用户不走平台购买页:用户在你的渠道付费后,由你的服务端调 generateForSoftware 发正式授权码,后续激活/校验链路与场景 A 相同。

5.4 上线验证清单

  • 用一台未登记为调试机的电脑访问产品详情页,确认正常展示
  • 购买页「调试模式」徽标
  • 真实小额支付 → 发码 → 激活成功
  • 开发者中心订单列表能看到该笔订单(测试订单不会出现在此)

六、上线后运营

操作 入口 说明
查看订单 开发者中心 → 我的订单 仅生产订单;测试订单不参与分润与对账
用户解绑 用户个人中心 / 开发者人工授权 换绑配额:解绑后 30 天内不可再次解绑
版本迭代 产品编辑 → 保存草稿 / 提审 编辑会再次异步同步 Waffo Test,可持续用调试机验证新版本
退款 Waffo Dashboard 买家发起工单、商家在 Dashboard 审核;测试订单退款仅吊销测试授权
下线调试 调试设备面板移除设备 建议版本稳定后移除调试机,避免误用测试链路

七、常见问题(FAQ)

Q1:调试期需要修改客户端代码或配置吗? 不需要。环境路由由平台服务端根据「机器码是否在调试设备列表」自动判定,SDK 调用完全一致。

Q2:调试机上拿到的授权码能给真实用户用吗? 不能也不建议。T- 码只在测试表有效,且调试数据不参与任何生产逻辑;给真实用户发测试码会导致其无法通过正常校验路径获得售后保障。

Q3:调试需要花钱吗? 不需要。测试支付通道使用测试卡,不发生真实扣款;测试订单不参与分润/结算。

Q4:调试会影响我的产品审核吗? 不会。调试数据与审核完全解耦;草稿状态即可调试,提审与否由你决定。

Q5:多台电脑都想调试怎么办? 每个产品最多登记 3 台调试设备;在调试设备面板添加/移除即可。

Q6:产品形态是「仅推广」或没配置价格,能调试购买吗? 不能。仅推广产品不同步 Waffo 商品目录;无有效价格时测试商品同步会失败。此类产品只能调试试用与校验链路。

Q7:忘记关调试开关就上线了会怎样? 影响范围仅限你登记的那台机器——它仍走测试链路;所有真实用户不受影响。确认稳定后在调试设备面板移除即可。

八、集成清单(上线前逐项核对)

场景 A(平台完整链路)

  • 在幂栈网发布产品,售卖方式选「先用后付」
  • 配置试用天数(建议 7~14 天)
  • 配置授权版本(edition code + 名称 + 价格 + 功能清单)
  • 确认「平台代收授权费」已勾选(先用后付强制勾选)
  • 记下 productUniqueCode
  • 拷贝 SDK 源码到项目(见 2.3.1)
  • 实现 claimTrial → 首次启动领取试用
  • 实现 activate → 用户输入授权码激活
  • 实现 verifyCached → 点击付费功能时校验
  • 实现 purchaseUrl → 未授权跳转购买页(统一用国际站 .app,无需选择站点)
  • 实现本地凭证持久化(licenseCode + activationToken
  • 实现 edition 层级比较逻辑(见 3.2)
  • 实现 LicenseError 错误码处理(见 3.3)
  • 登记调试机并走通双环境调试清单(见 4.4)
  • 提审 → 审核通过 → 上线验证(见 5.4)

场景 B(订单不在平台)

  • 在幂栈网发布产品,开启 licenseEnabled
  • 配置授权版本(edition code + 名称);如需平台代收,勾选「平台代收授权费」并填写价格
  • 在开发者后台获取 licenseApiSecret
  • 记下 productUniqueCode
  • 搭建服务端保管授权密钥 licenseApiSecret,实现发码接口(密钥不可下发到客户端)
  • 客户端拷贝 SDK 源码到项目
  • 服务端实现 generateForSoftware(发码)
  • 服务端实现 upgradeForSoftware(升级/续费)
  • 客户端实现 activate / verifyCached
  • 实现本地凭证持久化
  • 实现 edition 层级比较逻辑(见 3.2)
  • 实现 LicenseError 错误码处理(见 3.3)
  • 登记调试机验证 T- 码链路(见 4.4)
  • 提审 → 审核通过 → 上线验证(见 5.4)

附录:SDK 仓库(GitHub)

github.com/mizhanchengxi/powersoftware-license-sdk
├── node/      SDK 源码(ESM, 零依赖, 单文件)
├── python/    SDK 源码(py3, 零依赖, 3 个文件)
├── java/      SDK 源码(Java 8+, 零依赖, 3 个文件)
└── docs/      SDK 规范文档

三个包均提供:machineCode() / sign() / LicenseClient(含 activate / verify / deactivate / claimTrial / generateForSoftware / upgradeForSoftware / verifyCached / purchaseUrl)。

本文档为 客户端软件授权接入指南(CLIENT_SOFTWARE_GUIDE) 的 v4 升级版,完整覆盖其全部内容,并新增双环境调试与草稿/提审能力。