IJPay 2.9.13 已发布。这个项目封装了微信支付、QQ 支付、支付宝、银联、京东支付和 PayPal 等常见支付渠道,并覆盖多种支付相关接口。它不依赖第三方 MVC 框架,因此既能用于 Spring Boot,也能嵌入传统 Java Web 项目、定时任务或独立服务。
由于现有摘要没有列出 2.9.13 的逐项变更,本文不推断具体 API 调整,而是重点讨论这种框架无关支付工具的接入方式,以及升级时真正需要验证的边界。
为什么“不绑定 MVC”很重要
支付 SDK 最适合停留在基础设施层。下单、签名、验签、退款和查询属于支付能力;HTTP 参数解析、路由和响应渲染则属于应用框架。两者分离后,同一套支付代码可以被 REST 接口、消息消费者和后台补偿任务共同调用。
建议把业务系统拆成三层:
- Controller 或 Handler:接收请求,执行基础参数校验。
- PaymentService:处理订单状态、幂等和渠道选择。
- IJPay 适配层:构造渠道参数,调用支付接口并验证通知签名。
这样做还能限制升级影响。IJPay 版本变化通常只需要在适配层消化,不必把渠道 SDK 的参数对象传播到全部业务模块。
一个可运行的渠道适配示例
下面是一个不依赖 MVC 框架的最小 Java 示例。它先展示统一支付入口的结构,其中 IjPayGateway 使用模拟结果,因此可以直接运行;接入实际项目时,再把 pay 方法中的模拟逻辑替换成 IJPay 2.9.13 对应渠道的下单调用。
import java.math.BigDecimal;
import java.util.Map;
public class PaymentDemo {
enum Channel { WECHAT, ALIPAY, PAYPAL }
record PayRequest(String orderNo, BigDecimal amount, Channel channel) {}
record PayResult(String orderNo, String status, String providerTradeNo) {}
interface PaymentGateway {
PayResult pay(PayRequest request);
}
static final class IjPayGateway implements PaymentGateway {
private final Map<Channel, String> merchantIds;
IjPayGateway(Map<Channel, String> merchantIds) {
this.merchantIds = Map.copyOf(merchantIds);
}
@Override
public PayResult pay(PayRequest request) {
if (request.amount().signum() <= 0) {
throw new IllegalArgumentException("amount must be positive");
}
String merchantId = merchantIds.get(request.channel());
if (merchantId == null || merchantId.isBlank()) {
throw new IllegalStateException("missing merchant configuration");
}
// Replace this block with the IJPay 2.9.13 API for the selected channel.
String providerTradeNo = request.channel() + "-" + request.orderNo();
return new PayResult(request.orderNo(), "CREATED", providerTradeNo);
}
}
public static void main(String[] args) {
PaymentGateway gateway = new IjPayGateway(Map.of(
Channel.WECHAT, "wx-merchant-demo",
Channel.ALIPAY, "ali-merchant-demo",
Channel.PAYPAL, "paypal-merchant-demo"
));
PayResult result = gateway.pay(new PayRequest(
"ORDER-20250308-001",
new BigDecimal("19.90"),
Channel.WECHAT
));
System.out.println(result);
}
}
将文件保存为 PaymentDemo.java 后,可以直接验证结构:
javac PaymentDemo.java
java PaymentDemo
生产接入时,不要让 Controller 直接调用具体渠道。可以在 IjPayGateway 内按 Channel 分派到微信、支付宝或 PayPal 的实现,并将 IJPay 返回值转换成系统自己的 PayResult。这样即使渠道字段发生变化,订单领域模型也能保持稳定。
回调处理比下单更需要约束
支付通知不能按普通 HTTP 请求处理。无论使用哪个渠道,都应在适配层统一执行以下步骤:
- 保留原始请求体和必要请求头,避免参数重组导致验签失败。
- 使用对应渠道的证书或密钥验签,验签前不能更新订单状态。
- 以商户订单号和渠道交易号建立幂等约束。
- 核对金额、币种、商户号和应用标识,不能只检查“支付成功”。
- 在本地事务提交后返回渠道要求的成功响应;失败时允许渠道重试。
数据库层可以建立唯一索引,阻止重复通知产生二次入账:
CREATE UNIQUE INDEX uk_payment_provider_trade
ON payment_record (channel, provider_trade_no);
CREATE UNIQUE INDEX uk_payment_order_channel
ON payment_record (order_no, channel);
日志中应记录订单号、渠道交易号和错误码,但不要输出私钥、完整签名材料、用户身份信息或未经脱敏的回调正文。
升级到 2.9.13 的检查清单
升级支付依赖不应只验证“项目能编译”。更可靠的做法是先在测试环境逐渠道完成下单、回调、查询、关闭和退款,并检查序列化字段、签名算法、证书加载方式及异常类型是否变化。
还需要关注这些边界:
- 锁定依赖版本,避免构建时解析到不可预期的新版本。
- 将商户密钥和证书交给密钥管理服务,不要放进源码仓库。
- 对外部调用配置连接、读取和整体超时,并为查询类接口设置有限重试。
- 下单请求使用业务幂等键;超时后先查询支付状态,不要立即重复扣款。
- 保留旧版本制品和配置回滚方案,同时确认数据库变更是否向后兼容。
IJPay 的价值不只是减少渠道 API 调用代码,更在于让支付能力脱离具体 Web 框架。真正稳健的落地方式,是再用一层内部接口隔离 IJPay 与订单业务,并通过沙箱、回调重放和对账测试守住资金状态的一致性。