流程总览

  1. 用户在 /license/buy 填写域名并选择支付渠道。
  2. 服务端解析域名与回跳地址,创建(或复用)待授权记录与订单,向支付渠道下单,得到二维码内容。
  3. 跳转到 /pay/{out} 展示二维码,前端轮询 /pay/status/{out}。
  4. 支付成功后(回调或主动查询),订单置为已支付并签发授权令牌。
  5. 轮询返回 paid 及跳转地址,跳转到 /pay/done/{out} 或回跳站点并附带令牌。

GET /license/buy

查询参数:

参数说明
domain要授权的域名(可含 www.、协议、路径,服务端会规范化)。
return可选。支付完成后回跳的地址。

域名解析顺序

  1. 优先使用 domain 查询参数。
  2. 若 domain 为空,则回退到 Referer 请求头的主机名。

Referer 处理规则:仅接受 http 或 https 协议的 Referer;主机名规范化(小写、去端口、IDN→punycode)后,若为 kingcms.com 或 download.kingcms.com 则忽略(视为空)。

无论来源如何,主机名都会先规范化(去 www.、小写、punycode),再计算根域名(基于 Public Suffix List,支持 com.cn 等多段后缀,例如 shop.example.com.cn → example.com.cn)。页面展示与授权均以根域名为准。

当域名来自 Referer 且该主机名与根域名不同(即访问的是子域名,如 shop.example.com),页面会显示「来自 <host>」提示,说明授权将覆盖整个根域名及其全部子域名。

return 校验规则

必须是 http 或 https,且其主机名(规范化后)必须等于所授权的主机名(即 domain 参数规范化后的完整主机名,而非根域名)。不满足则忽略该参数。

当域名由 Referer 推导而来(domain 参数为空)时,若未显式提供合法的 return,则默认使用 Referer 本身作为回跳地址(同样需通过上述主机名匹配校验)。

已授权行为

页面会显示价格、可用渠道。若该根域名已有有效授权(active)或已签发有效令牌,页面会一并展示该令牌,并标记 HasLicense / HasToken,提示用户无需重复购买。

价格展示

  • 原价(list price):配置的标价。
  • 优惠价(promo price):配置的促销价,受「促销开关」控制。
  • 生效价:促销开关开启且促销价 > 0 时取促销价,否则取原价。

所有价格均以「分」为单位在服务端计算与校验,前端展示的金额仅供参考,下单金额一律以服务端计算为准。

POST /license/buy

表单字段:

字段必填说明
domain是要授权的域名,服务端规范化后取根域名。
return否回跳地址,需通过主机名匹配校验。
channel是alipay 或 wechat。
coupon否优惠码(服务端会转大写并去除首尾空白)。
buyer_name否购买人姓名。
buyer_company否公司名称。
buyer_email否联系邮箱。
buyer_phone否联系电话。

需通过公开 CSRF 校验。

校验与处理

  • 域名规范化后计算根域名(基于 Public Suffix List,支持 com.cn 等多段后缀)。
  • 价格未配置(≤0)或渠道未启用时返回错误提示。
  • 若填写了优惠码:服务端校验优惠码存在、状态为未使用、批次未过期,并按优惠类型计算最终金额(见下)。
  • 按根域名查找或创建待授权记录,生成商户订单号(KC + 北京时间 yyyymmddhhmmss + 6 位随机数字)。
  • 调用渠道下单接口获取二维码内容,写入订单,303 跳转到 /pay/{out}。
  • 若使用了优惠码,订单创建后立即占用该优惠码(状态置为 reserved 并绑定订单与域名);占用失败则关闭订单并提示优惠码已被使用。
  • 可选购买人字段在授权记录存在对应列时写入。

优惠码与最终价格

优惠码在服务端解析,客户端提交的价格一律不信任。优惠类型:

  • 券后价(final):最终金额直接等于优惠码设定的金额。
  • 立减(off):最终金额 = 生效价 − 优惠金额。

最终金额最低为 ¥0.01(1 分),低于此值按 1 分计。

优惠码为一次性:下单时占用(reserved),支付成功时消费(used);若订单在 30 分钟内未支付,占用会被释放回未使用状态,可再次使用。

POST /license/coupon

公开的优惠码试算接口,用于在购买页实时显示折后价。需通过公开 CSRF 校验,并受每 IP 限流(超出返回 429)。

表单字段:

字段说明
code优惠码(服务端转大写并去除首尾空白)。
domain可选,用于上下文展示。
csrf公开 CSRF 令牌。

返回 JSON:

{
  "ok": true,
  "list": "2999.00",
  "promo": "299.00",
  "final": "199.00",
  "message": "优惠码已应用"
}
  • ok:优惠码是否可用。
  • list:原价(元)。
  • promo:生效价(元,促销开关开启时为促销价,否则为原价)。
  • final:应用优惠码后的最终价(元);不可用时等于 promo。
  • message:提示文案(已应用 / 无效 / 已使用 / 已过期 / 限流等)。

该接口仅用于展示,实际下单金额仍以 POST /license/buy 服务端计算为准。

GET /pay/{out}

展示订单金额、渠道、二维码(/pay/qr/{out}.png)、轮询地址(/pay/status/{out})与完成地址(/pay/done/{out})。若订单已支付则直接跳转到完成页。

GET /pay/qr/{file}

返回订单二维码 PNG(256px,中等纠错)。文件名形如 {out}.png。

GET /pay/status/{out}

返回 JSON:

  • 待支付:{"status":"pending"}
  • 已支付:{"status":"paid","redirect":"<url>"}。若订单有合法回跳地址且已签发令牌,redirect 为回跳地址并附带 token 查询参数;否则为 /pay/done/{out}。
  • 不存在:404,{"status":"not_found"}

对处于待支付且创建超过 5 秒的订单,服务端会(同一订单每 5 秒最多一次)主动向渠道查询并结算。

GET /pay/done/{out}

展示完成页:订单、令牌、域名、回跳地址,以及「回跳地址 + token」的完整链接(若两者都存在)。页面同时提供授权证书查询链接(/license/check?domain=<domain>),方便用户核对授权状态。

支付回调

POST /pay/notify/alipay

  • 校验支付宝异步通知签名(使用配置的支付宝公钥)、app_id 一致。
  • 仅处理 trade_status 为 TRADE_SUCCESS / TRADE_FINISHED;其他状态直接应答 success 不处理。
  • 校验金额与订单一致。
  • 成功应答纯文本 success。

POST /pay/notify/wechat

  • 校验微信支付回调签名(Wechatpay-Timestamp / Wechatpay-Nonce / Wechatpay-Signature),时间戳偏差超过 5 分钟拒绝。
  • 使用 APIv3 密钥解密 AEAD_AES_256_GCM 资源,校验 trade_state 为 SUCCESS、金额一致。
  • 成功应答 {"code":"SUCCESS"}。

幂等性

结算通过「将订单从待支付原子地置为已支付」实现:只有真正发生状态迁移的那一次才会签发授权与记录收款,重复回调或重复查询不会重复签发。优惠码的消费同样只在状态迁移成功时发生一次。