构建插件里扫码付款自动发 licence
一个 Java 工具做成 Gradle/Maven 插件分发,想收费,就希望用户在命令行或者构建的时候直接扫码付款、licence 自动发到本机、构建接着跑,全程不用人工干预。整个链路梳理下来其实不复杂,但有几个点不注意会出问题。
整体流程是这样:用户跑构建的时候插件发现没 licence,调服务端创建一个订单(订单绑死本机指纹),服务端调微信 Native 下单拿到一个 code_url;插件把这个 code_url 在控制台打成 ASCII 二维码;用户扫码付钱;微信异步回调服务端,或者服务端主动查单兜底;服务端确认收款后凭订单里绑的指纹签发 licence、落库;插件轮询把 licence 拉下来,写盘前用内嵌公钥重新验签一遍、再比对一次本机指纹,确认无误才写进 ~/.myapp/app.lic。
几个关键点分开说。
订单绑指纹,金额服务端定
创建订单的时候,客户端把本机算的指纹一起传上去,服务端把指纹和订单绑死。金额由服务端的价格表决定,客户端传什么都改不了。这样防的是改价——客户端是用户能改的,金额判断不能放客户端。
实付金额必须等于订单金额
微信回调或者查单拿到”已支付”之后,不能直接就发 licence,得先校验实付金额和订单金额(都是整数分)是不是相等:
public synchronized void markPaid(Order order, int paidAmountFen, String transactionId) { |
防的是小额套大:下个 1 分钱的订单,想办法让回调带上高价套餐的订单号,光看”支付成功”就放行就亏了。实付必须严格等于应收。
回调要幂等
微信的回调会重试,同一笔可能来好几次。markPaid 开头判一下状态,已经 PAID 的直接返回,不重复签发 licence。synchronized 是单机兜底并发,多实例就得靠数据库唯一键或者分布式锁。
异步回调加主动查单,双保险
回调不是 100% 可靠,网络抖动、服务重启都可能丢。所以除了等回调,插件那边轮询的同时,服务端也配个定时任务主动查单,哪个先确认收款都行,都走同一个 markPaid。两条路进同一个函数,才不会出现回调能过、查单过不了的不一致。
回调验签用平台公钥模式
微信回调的签名是对原始请求体验算的,接回调时 body 得原封不动拿到(@RequestBody String),不能让框架先反序列化成对象,字段顺序一变签名就对不上。验签用平台公钥模式(固定的公钥文件)比证书模式省心,不用管证书轮换。
插件拿到 licence 要二次验签
服务端签的 licence 是 JWT,插件拿到之后别直接信,用插件自己内嵌的公钥重新验签一遍,再比对 licence 里的指纹和本机指纹是不是一致,确认才写盘。防的是传输途中被换包、或者返回被篡改。
控制台弹二维码要看场合
ASCII 二维码只有 System.console() != null(真正的交互式终端)的时候才打,在 CI 或者 IDE 内嵌的控制台里打出来是乱码,这时候就静默走离线提示(告诉用户去网页买或者手动放 licence 文件),别卡着构建。
插件里的 HTTP 尽量零依赖
构建插件对依赖体积敏感,引一个 OkHttp 加一堆传递依赖不划算。直接用 JDK 自带的 HttpURLConnection 就够了,JSON 响应也不引库,手写个极简的字符串匹配把要的那几个字段抠出来。轮询用 Thread.sleep 循环,设个超时(比如 5 分钟)别一直挂着。
这条链路打通之后,用户体验就是:跑构建 → 没 licence → 控制台弹二维码 → 扫码 → licence 自动到账 → 构建继续,全程不用找我。里面真正麻烦的不是支付本身,是金额校验、幂等、双通道这些”钱对不对得上”的防护,还有构建插件环境下那些依赖和交互的取舍。
