Shopark 授权补推

APay 拥有账号和订阅,Shopark 的 Saas 应用连接商户并通过 Go RPC 下发店铺授权。支付与订阅账本仍由核心接口维护。

套餐目录与授权快照

外部授权以 Shopark plugins/app/Saas/PARTNER_APAY.md 为准,Shopark 原生计费规则不能替代 APay 账本。APay 映射事实源统一在 app/extensions/shopark/shared/plans.ts,主题 app/themes/shopark/data/plans.ts 仅转出。主题与后台扩展共享签名客户端、权益解析、持久版本、自检和补推存储,后台强制重推不能绕过 planKey。公开目录只有七个价格商品,全部人民币:

planKey展示名月付年付planCode
free未付费 / 已到期不售卖不售卖free
showcase企业展示版不提供399 元/年showcase-yearly
starter入门经营版199 元/月1,999 元/年starter-monthly / starter-yearly
professional专业经营版299 元/月2,999 元/年professional-monthly / professional-yearly
premium旗舰经营版599 元/月5,999 元/年premium-monthly / premium-yearly
enterprise企业定制版合同定价合同定价enterprise-custom(显式 enterprise key)

订阅商品元数据声明 shopark_plan_key、shopark_plan_code、shopark_store_limit、level、subscription_cycle,等级依表顺序为 0–5。已知旧编码仍可用于存量授权:裸 enterprise、enterprise-monthly/yearly 按历史规则解释为 premium;显式 shopark_plan_key=enterprise 始终表示企业定制。未知编码或 key 不参与解析,不按商品展示名猜测。Free 和企业定制不进入公开固定价格目录;价格、周期、等级或多语言价格不符合规范的商品也不进入目录。

所有下发的 license 包含 { planCode, planKey, expireAt, maxStores, version }。GET /api/shopark/entitlement 新增 data.planKey,只计算已经开始且仍有效的 active 订阅周期。退款撤销、取消生效和到期回落 free;到期取消在周期结束前保留付费权益。expireAt=null 表示长期有效(Shopark 存为 2099-12-31T23:59:59Z),不表达试用。APay 合作方快照不能表达 Shopark 原生 14 天 Professional 试用,官网不承诺该试用。

版本由私有设置 shopark_license_version.<userId> 按用户持久保存,通过比较并交换分配,并在读取订阅之前取得。并发、重启、同毫秒及系统时钟回拨不会复用成功取得的版本。该状态不得公开或在发布/回滚时清空,无需核心 Schema 迁移。

权益按 APay 用户共享到所有店铺,maxStores 是开店数,资源额度按每家店计算。建店与配额展示使用 Shopark 返回的 quota,包含平台上限及待恢复创建占用;不从 APay 套餐伪算可用名额。不确定建店请求在弹窗重新打开后仍保留原 requestId 与冻结参数。replayed=true 表示同一家店,licenseApplied=false 必须展示待同步。store/list 同时返回单店经营快照(stats:在售/总商品、近 30 天/累计已付款订单与成交额、成交买家数、近 30 天 UV/PV、lastOrderAt)与商家多店汇总 summary(含已开店地区/时区与结算币种分布);store/overview 为后台 /admin/plugins/shopark/stores 提供全平台开店分布与经营总览。

planKey商品 / 同时上架内容页域名员工(不含店主)存储AI 点数/月
showcase20510200 MB0
starter500不单独限额125 GB100
professional5,000不单独限额3520 GB300
premium20,000不单独限额1015100 GB1,000
enterprise当前采用旗舰资源基线,尚未实现合同额度覆盖

存储按十进制(1 GB = 1,000,000,000 字节)。资源与 AI 计量由 Shopark 执行,APay 不复制实现。AI 点数逐月补发,年付也逐月发,不结转;升级只补当期差额且不清空已用点数。已知差距:合作方契约没有账单周期锚点,APay 店铺的 AI 池目前按 UTC 自然月刷新,不能承诺按账单日补发。Shopark 没有合作方用量接口,APay 不展示伪造的降级前用量数字。

老店绑定与降级

绑定使用 challenge → verify → confirm 三步。店铺卡片按 planKey 展示本地化名称,已知 edition 编码仅作兜底。Verify 逐店展示 currentPlanKey/currentExpireAt → nextPlanKey/nextExpireAt。任一店铺 downgrade=true 都要求用户主动勾选确认,默认未勾选;之后 confirm 才传 acknowledgeDowngrade=true。bind:acknowledge_required 显示独立业务错误。Confirm 的 failed[] 显示“待同步”,不把账号绑定成功说成授权同步完成,由补推恢复。

绑定后原 Shopark 订阅停止续费,改由 APay 计费。降级快照在 Shopark 立即生效:保留已有超额数据,但限制新增商品、上传、员工、域名和 AI 使用。APay 保留有效订阅期间禁止降级购买的核心门禁,主题在下单前说明。此次没有新增按比例计费或预约降级购买流程。

GET /api/shopark/cron/license-sync 在所有环境都要求 Authorization: Bearer $CRON_SECRET。返回 { data: { busy, checked, pushed, failed, pending, cursor, lastFullScan } };已有任务持有租约时只返回 { data: { busy: true } }。未鉴权返回 401,未配置合作方返回 503,存在失败积压时返回 503 并附批次结果。存储或网络异常保持失败,不能报成功。

任务按用户 ID 升序扫描,持久保存游标和失败用户,扫描结束后重新开始。单批最多扫描 100 个用户,另重试最多 25 个失败用户,处理预算为 45 秒。付费用户可补开通失败的账号,免费用户只同步已有 Shopark 映射。每次重新计算当前完整权益,覆盖退款、取消与到期,没有三天回看限制。Shopark 返回的 pendingRequests 表示建店仍待恢复,同样进入失败重试。状态存于私有设置 shopark_license_reconcile_state,不得登记为公开设置。

核心调度器配置:路径 /api/shopark/cron/license-sync、方法 GET、周期 5(分钟)、useCronSecret=true、enabled=true。Serverless 部署使用外部调度器并发送同一鉴权头。完整扫描时间取决于用户量、延迟和频率;验收须确认扫描结束且积压为零,不能以历史 HTTP 成功代替。

分钟周期须保存为 JSON 数字 5,不能是字符串 "5";非法周期会兜底成每日。本地 Shopark 已开启租户路径且允许本机 IP 时,如果超级店铺 ID 为 1,使用 SHOPARK_PARTNER_API_URL=http://127.0.0.1:5490/1/app/saas/partner/apay,由路径提供租户路由,无需临时注入请求头。环境变量修改后重启本地 APay 进程;生产继续使用其配置的超级店铺域名。

只有 license/sync 收到明确 HTTP 429 时会有限重试,在原 25 秒请求期限内最多三次。每次使用新 nonce 重新签名,完整快照和版本保持不变。Retry-After 秒数最多等待五秒,其他值等待一秒;网络不确定、业务拒绝、建店和绑定不自动重放。重试耗尽或建店仍待恢复时,仍保留为持久队列中的失败项。

建店在调用 Shopark 前冻结参数。不确定的结果必须沿用同一个 requestId 重试;中断超过十分钟后可按域名和随机管理员身份恢复同一家店;浏览器丢失请求 ID 时后台补推也会继续恢复。未完成的创建占用开店额度,不能用新请求绕开。licenseApplied=false 表示店铺已创建、授权待同步。老商家绑定成功后的 failed[] 同样独立表示待补推,不能将账号绑定成功等同于授权完成。

先部署 Go,完成店铺 license_source、license_version 和 edition 扩至 64 字符的迁移;在 Shopark 的 plugins/ 目录执行 php shopark db:sync Saas 增加建店恢复字段,再部署 PHP 和 APay。重新开放购买前先对账存量账号。回滚保留新增列;仍由 APay 管理的账号不能回退 Go 版本校验或重新启用 Shopark 计费。

上线前核对合作方可达性、实际激活的 Shopark 主题、带合法标记的在售订阅商品、平台建店上限、完整补推结果,以及真实购买、退款、到期和绑定流程。构建与隔离测试通过不能证明生产链路可达。

上线与回滚

  1. 确认 Shopark Go 迁移、php shopark db:sync Saas 与 PHP 已部署且支持显式 planKey。须检查实际部署版本,不能把本地提交当成生产证据。
  2. 先审阅 APay 存量商品 ID、价格、币种、元数据与引用。node --experimental-strip-types app/themes/shopark/scripts/generate-plan-seed.mjs 生成 PostgreSQL 种子,只新增缺失商品,不删除商品或重写既有合同,假定部署基础币种为 CNY。已有冲突行单独审阅修正,保留 ID 与订阅金额/周期;不得把种子作为自动部署迁移执行。
  3. 审阅后发布 APay。分批调用带鉴权的 license-sync,直到 lastFullScan 晚于本次发布且 pending=0。503、failed[] 或 pendingRequests>0 都属于未完成。继续保留周期补推任务。
  4. 使用真实账号验证购买、升级、有效期内降级购买被拦截、退款、到期以及有/无降级的老店绑定确认;到期后再验证购买较低套餐。先核对实收币种与金额,才能声明真实支付验证。

失败时暂停新售卖,恢复经审阅的 APay 制品与商品配置,不删除订单、订阅、映射、补推状态或版本计数。Shopark 保留新增 Schema 与外部计费隔离。回滚代码必须保留显式 planKey 和持久版本,不能退回省略企业定制 key 的旧客户端。恢复权益时根据目标 APay 状态生成更大版本的新快照,不重放旧版本。商品修改与真实账号验收属于单独审阅的操作,本次开发不执行生产 SQL、真实扣款或发布。

回归:node app/themes/shopark/scripts/check-plans.mjs,同时作为 Shopark 构建守卫执行。npm run build 后运行 node --experimental-strip-types app/themes/shopark/scripts/check-reconcile-artifact.mjs,使用临时 SQLite 与本地签名上游,覆盖部分失败 503、待恢复建店、重启恢复、版本递增、六档 key、撤销/到期、并发租约与私有状态隔离。有 Shopark 源码及已安装的 PHP 依赖时,php app/themes/shopark/scripts/check-partner-auth.php /path/to/shopark/plugins 使用实际中间件与内存 RPC/Redis,覆盖双方 HMAC 固定向量、错误签名、时间戳过期、nonce 重放与去重存储失效时拒绝请求;不启动 Shopark 或访问其数据库。真实 Shopark 配置后另运行 smoke-partner.mjs 验部署可达性;本地契约测试不能证明线上接入成功。