简介:IJPay是一款面向Java开发者的聚合支付工具包,集成了微信支付、QQ支付、支付宝、京东支付、银联及PayPal等主流支付渠道,并封装了各类常用接口。它不依赖第三方MVC框架,可作工具类直接嵌入任意业务系统,帮助开发者跳过繁琐的支付对接流程,快速完成支付模块开发。资源共579个文件,压缩包仅3.23MB,其中以271个Java源码文件为主体,辅以31个Markdown说明文档、63个HTML页面及配套的properties配置、JS/CSS和XML等,涵盖代码示例、配置说明与前端展示资源,便于按需查阅与二次开发。目前已有245人学习下载,适合具备一定Java基础、希望快速集成多种支付方式的开发人员学习借鉴。
1. 聚合支付 IJPay:为什么支付模块值得单独封装
一个 Java 项目里同时接微信支付和支付宝,是件很磨人的事。微信走 API v3 要配证书序列号,支付宝走 RSA2 要管理公钥私钥,两边报文结构完全不一样,等这两个渠道调通,一周时间就没了。更尴尬的是,微信的 SDK 和支付宝的 SDK 各自带一套 HTTP 工具和签名实现,同时引进来还可能出现依赖冲突。
IJPay 这个支付开发包解决的就是这个问题。它把微信支付、QQ 支付、支付宝支付、京东支付、银联支付、PayPal 支付这些常用渠道的接口统一封装成工具方法,不依赖任何第三方 MVC 框架,你现有的 Spring Boot、JFinal、甚至纯 Servlet 项目都能直接嵌进去。适合需要快速接入多个支付渠道的程序开发同学,尤其是小程序微信支付 v3 对接、Java 后台接入 PayPal 支付这类场景,能省掉大部分重复的报文组装和验签代码。
2. IJPay 的模块设计与 Maven 引入:先看清支付单据长什么样
2.1 按支付渠道拆分 jar,而不是一个大而全的包
很多支付 SDK 会把所有渠道塞进一个 jar,引入后整个依赖树都被撑大。IJPay 反着来,每个支付渠道一个独立模块:IJPay-WxPay、IJPay-AliPay、IJPay-QQ、IJPay-JdPay、IJPay-UnionPay、IJPay-PayPal。用一个渠道就引一个包,互不污染。
这个设计有个实际好处:如果项目只用微信支付接口,依赖树里就不会出现支付宝的 SDK 类,避免了两个 SDK 里同名的SignType、HttpUtils之类工具类冲突。我在一个老项目里就踩过这种坑——上游传下来的 pom 同时引了两个支付 SDK,启动时出现NoSuchMethodError,排查半天才发现是传递依赖把类覆盖了。
IJPay 的项目目录里能看到jfinal.bat和mvnw.cmd这类构建脚本,jfinal.bat是作者用来启动示例 demo 的,示例工程基于 JFinal,但 SDK 核心层并没有依赖 JFinal。实际接入时你完全可以只引 jar,不需要引入 JFinal 框架。
2.2 按需引入 Maven 依赖
以微信支付 v3 为例,pom 里这样配:
<dependency> <groupId>com.github.javen205</groupId> <artifactId>IJPay-WxPay</artifactId> <version>使用中央仓库最新 release</version> </dependency> <!-- 只用支付宝就换成这个 --> <dependency> <groupId>com.github.javen205</groupId> <artifactId>IJPay-AliPay</artifactId> <version>使用中央仓库最新 release</version> </dependency>版本号建议直接去 Maven 中央仓库查,com.github.javen205这个 groupId 下的IJPay-WxPay等 artifactId 就是。注意不同大版本的 API 略有调整,2.x 和 1.x 的配置类名不完全一致,锁定一个版本后不要混用。
2.3 配置类:商户参数不写死在代码里
支付渠道的配置项包含 appId、商户号、API 密钥、证书路径、回调地址,这些在不同环境(dev、test、prod)下值都不同。常见做法是抽一个配置类,用@ConfigurationProperties绑定到 yaml:
ijpay: wxpay: app-id: wx**************** mch-id: 16******89 api-v3-key: 你的APIv3密钥 cert-serial-no: 证书序列号 private-key-path: classpath:/cert/apiclient_key.pem alipay: app-id: 2021************ private-key: MIIEvQIBADANB... alipay-public-key: MIIBIjANBgkq... sign-type: RSA2 gateway: https://openapi.alipay.com/gateway.do@Component @ConfigurationProperties(prefix = "ijpay.wxpay") public class WxPayProperties { private String appId; private String mchId; private String apiV3Key; private String certSerialNo; private String privateKeyPath; // getter / setter 省略 }配置文件里放私钥要注意安全性,生产环境建议用环境变量或配置中心覆盖,私钥明文进 git 仓库属于低级事故。.pem证书文件放在classpath:/cert/下,构建时注意不要把私钥打进前端资源包。
这里说明一下,IJPay-WxPay的 v3 接口在部分版本中通过WxPayV3Service或WxPayApiConfig组装请求,你引入的 jar 里如果找不到这两个类,搜索ApiConfig和Kit结尾的类即可,工具方法的命名风格是统一的。
3. 微信支付 v3 接入实战:从下单到回调验签
3.1 初始化微信支付 Bean
微信支付 API v3 使用商户私钥对请求签名,服务端用平台证书或公钥验签。IJPay 把证书加载和签名逻辑封装在配置类里:
@Configuration public class WxPayAutoConfiguration { @Bean public WxPayV3Service wxPayV3Service(WxPayProperties props) throws IOException { PrivateKey privateKey = WxPayKit.getPrivateKey( new ClassPathResource(props.getPrivateKeyPath()).getInputStream()); WxPayV3Service service = new WxPayV3Service(); service.setAppId(props.getAppId()); service.setMchId(props.getMchId()); service.setApiV3Key(props.getApiV3Key()); service.setCertSerialNo(props.getCertSerialNo()); service.setPrivateKey(privateKey); return service; } }mchId是商户号,apiV3Key是 API v3 密钥,在微信商户平台「账户中心-API 安全」里设置。certSerialNo是商户 API 证书的序列号,privateKey是证书对应的私钥文件内容转换出来的对象。如果项目用的版本里没有WxPayV3Service,按照同样的字段自己组装HttpRequest调WxPayApi.v3()也可以。
注意:API v3 密钥是一个 32 字节的字符串,不是 API v2 的 32 个字符的 Key,两者不通用。升级到 v3 后很多报错都出在这里。
3.2 Native 下单接口调用
以 PC 端扫码支付(Native)为例,下单核心逻辑:
public String createNativeOrder(String orderNo, Integer amount, String description) { Map<String, Object> params = new HashMap<>(); params.put("appid", wxPayV3Service.getAppId()); params.put("mchid", wxPayV3Service.getMchId()); params.put("description", description); params.put("out_trade_no", orderNo); params.put("notify_url", "https://api.example.com/pay/wx/notify"); params.put("amount", new Amount().setTotal(amount).setCurrency("CNY")); String result = wxPayV3Service.payV3( builder().build(), WxPayModel.NATIVE, params); // 返回结果里包含 code_url,生成二维码用 return JSON.parseObject(result).getJSONObject("data").getString("code_url"); }这里的payV3方法名在不同版本可能有差异,核心参数是固定的:appid是公众号或小程序 appId,mchid是商户号,out_trade_no是商户订单号,notify_url是异步通知地址,amount.total是订单金额。
金额单位是「分」,这是微信支付最容易踩的坑。订单 100.50 元,传给微信的 total 必须是 10050,写成 100.50 微信会直接返回参数错误。数据库设计金额时就用整数分存储,展示层再除以 100,一劳永逸地规避浮点精度问题。
3.3 异步回调验签与报文解析
下单后微信会把支付结果 POST 到notify_url,回调处理是整个支付流程里最容易出安全问题的一环——必须验签通过后才能更新订单状态。
@PostMapping("/pay/wx/notify") public String wxNotify(HttpServletRequest request) throws IOException { String body = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8); // 微信支付 v3 的验签需要请求头的 timestamp、nonce、serial、signature boolean verified = wxPayV3Service.verifyNotify( request.getHeader("Wechatpay-Timestamp"), request.getHeader("Wechatpay-Nonce"), request.getHeader("Wechatpay-Signature"), body ); if (!verified) { // 验签失败,返回非 2xx 状态码,让微信稍后重试 return "failure"; } // 解析回调报文,确认订单状态为 SUCCESS JSONObject data = JSON.parseObject(body).getJSONObject("resource"); // 对 resource 字段做 AES-GCM 解密 String plaintext = decryptResource(data.getString("ciphertext"), data.getString("nonce"), data.getString("associated_data")); // 更新订单状态,记录第三方流水号 return "success"; }验签的本质是确认这个回调确实来自微信服务器,而不是伪造请求。Wechatpay-Signature是微信用平台证书私钥生成的签名,用平台证书公钥验证。如果验签失败直接返回 HTTP 200,攻击者就能无限次尝试伪造回调。
回调报文里的订单数据在resource字段,是 AES-256-GCM 加密的,解密用的密钥就是 API v3 密钥。解密后能拿到out_trade_no、transaction_id、trade_state等字段。业务侧根据out_trade_no找到本地订单,比对金额一致后再置为已支付。
3.4 查询、关单与退款
支付结果异步通知不是百分百可靠,必须有主动查询兜底。查询接口按out_trade_no或transaction_id查即可:
// 主动查询订单状态 String queryResult = wxPayV3Service.queryOrderByOutTradeNo(orderNo); // 关单:超时未支付时关闭 String closeResult = wxPayV3Service.closeOrder(orderNo); // 退款:需要商户私钥和证书,refundNo 是商户退款单号 RefundModel refund = new RefundModel() .setOutTradeNo(orderNo) .setOutRefundNo(refundNo) .setAmount(new RefundAmount().setRefund(refundAmount).setTotal(orderAmount).setCurrency("CNY")); String refundResult = wxPayV3Service.refundV3(refund);退款接口要注意refund金额单位同样是分,且total必须是订单原始金额。退款是异步处理的,返回PROCESSING不代表成功,需要接退款结果回调或主动查退款单状态。另外,退款会消耗 API 证书的调用额度,生产环境要配合退款原因记录。
4. 支付宝、银联与 PayPal:一套模式下的差异点
4.1 支付宝:金额单位变成元,验签走 RSA2
支付宝接入用AliPayApi,配置对齐核心是这样:
AliPayApiConfig alipayConfig = AliPayApiConfigKit.init( appId, privateKey, alipayPublicKey, "RSA2", gateway);关键差异在于金额单位:微信传分,支付宝传元。同样是 100.50 元,支付宝传的字符串是"100.50",精确到小数点后两位。所以做一个统一的支付层时,内部统一用「元」还是「分」要定清楚,对接微信时乘 100,支付宝时直接转字符串。
支付宝的异步回调验签方式也和微信不同:
// request 是支付宝 POST 过来的参数,不用读 body,参数在 request 的 form 里 boolean signVerified = AliPayApi.rsaCheckV1( request.getParameterMap(), alipayConfig.getAlipayPublicKey(), "UTF-8", "RSA2"); if (signVerified) { String tradeStatus = request.getParameter("trade_status"); // TRADE_SUCCESS / TRADE_FINISHED 才表示支付成功 return "success"; // 必须原样返回 success,否则支付宝会重试通知 } return "failure";支付宝的通知是 form 表单格式,app_id、out_trade_no、total_amount、trade_status都是平铺参数。验签通过后判断trade_status,只有TRADE_SUCCESS和TRADE_FINISHED两种状态才要更新本地订单。回调返回字符串必须是success(不带引号),返回其他内容支付宝会按八次、两倍间隔重试。
4.2 银联:证书两层,敏感字段单独加密
银联支付(UnionPay)的逻辑跟微信、支付宝的 RESTful 风格不一样,它走的是报文 + 证书的老一代网关体系。IJPay 的 UnionPay 模块默认支持:
- 签名证书:商户私钥证书,用来对请求报文签名
- 加密证书:银联公钥证书,用来加密
cardNo、cvn2等敏感报文域 - 验签证书:验证银联返回报文的签名
// 银联请求分两种:前台(页面跳转)和后台(无页面) SupplierEndRequest req = new SupplierEndRequest(); req.setMerId(mchId); req.setOrderId(orderNo); req.setTxnAmt(amount); // 单位分,和微信一致 req.setTxnTime(yyyyMMddHHmmss); // 证书路径、密码在 sdk.properties 或自定义配置中设置 String result = UnionPayApi.supplierEnd(req);银联的txnTime是交易时间,格式固定为yyyyMMddHHmmss,同一商户一天内订单号不能重复。它的异步通知报文也不是 JSON,而是 key-value 拼装起来的明文 + 签名串,IJPay 封装了UnionPayApi的报文解析和验签方法,不需要自己拼验签串。做对账时注意银联的清算文件是 T+1 生成的,要按txnTime和acqInsCode匹配。
4.3 PayPal:先拿 Access Token,再两级确认
PayPal 的逻辑跟国内支付完全不同,它是 OAuth 2.0 授权模式:先创建订单拿到 PayPal 侧订单号,前端跳转 PayPal 支付,支付完成后 PayPal 回跳,后端再调用 capture 接口把钱真正划过来。
// 第一步:换取 access token String token = PayPalApi.getAccessToken(clientId, clientSecret, mode); // 第二步:创建订单 PayPalOrder order = PayPalApi.createOrder(token, amount, currency, returnUrl, cancelUrl); // 第三步:用户支付完成回跳后,捕获订单 PayPalApi.captureOrder(token, orderId);注意 PayPal 的金额是字符串,"100.50",没有整数分概念。捕获订单时要判断status == COMPLETED,而且创建订单和捕获订单之间的间隔不要太长,部分 PayPal 商户配置下订单超时后 capture 会失败。国内服务器访问 PayPal 接口有延迟,线上环境要调大connectTimeout和readTimeout,同时做好失败重试。PayPal 的 webhook 回调验签用的是事件签名,和国内渠道的签名算法不通用。
4.4 常用接口清单与差异对比
| 功能点 | 微信支付 v3 | 支付宝 | 银联 | PayPal |
|---|---|---|---|---|
| 金额单位 | 分(整数) | 元(字符串两位小数) | 分(整数) | 元(字符串) |
| 签名算法 | 商户私钥签名 + 平台证书验签 | RSA2 | 双证书(签名+加密) | OAuth 2.0 Bearer Token |
| 主动查询 | queryOrderByOutTradeNo | alipay.trade.query | UnifiedTradeQuery | getOrder |
| 退款 | API v3 退款接口 | alipay.trade.refund | 退货接口 | refundOrder |
| 异步通知格式 | JSON + AES 密文 | Form 表单 | key-value 串 | Webhook JSON |
| 回调失败重试 | 最多重试多次,间隔递增 | 8 次,2 倍间隔 | 间隔策略可配置 | Webhook 队列 |
QQ 支付从技术体系上看和微信支付同源(都走财付通网关),IJPay 的 QQ 模块在报文格式和签名方式上贴近微信 v2,配置QQPayConfig时注意区分 appId 和 mchId。京东支付则是独立的一套网关协议,JdPayApi的核心参数是ossMerchantNo和merchantNo,跟微信支付宝差异较大,但整体流程也是下单-回调验签-查询。
5. 收尾技巧:回调幂等、金额精度与排错链路
支付回调必须幂等,这是所有接入方的共识,但实现起来容易漏。微信和支付宝的异步通知机制都保证「至少一次」而不是「恰好一次」,这意味着同一笔订单可能收到多次成功回调。常见做法是更新订单状态时加条件更新:
@Transactional public void handlePaidOrder(String outTradeNo, String thirdTradeNo, long paidAmount) { Order order = orderMapper.selectByOutTradeNo(outTradeNo); // 状态已经支付过,直接返回,不重复处理 if (order != null && "PAID".equals(order.getStatus())) { return; } int updated = orderMapper.compareAndSetStatus( outTradeNo, "UNPAID", "PAID", thirdTradeNo, paidAmount); // 返回 0 说明订单状态已被其他回调改成 PAID,幂等完成 if (updated == 0) { return; } // 到这一步才允许发放在线时长、增加积分等业务动作 }数据库层用UPDATE ... WHERE status = 'UNPAID'做条件更新,比先查后更稳妥。如果支付结果要同步给多个下游系统,优先用本地消息表 + MQ 发送,而不是在回调线程里同步调用,回调线程超时会导致支付平台重发通知,形成连锁重复。
金额精度的问题在聚合场景下比单渠道更突出。统一的支付入口建议把所有金额定义为long(分),对外接微信、银联直接传;对接支付宝、PayPal 时用BigDecimal从分转换:
private String fenToYuanStr(long fen) { return BigDecimal.valueOf(fen).divide(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP).toString(); }排错时先看三件事:第一,打开 IJPay 的 HTTP 日志,请求和响应报文都会打到控制台或 log 文件,检查mchid和appid是否匹配;第二,确认服务器时间与标准时间偏差,微信支付 v3 的时间戳校验误差超过五分钟就会验签失败,NTP 同步不能省;第三,把回调接口的验签失败原因往后端日志里打印,微信回调失败时先确认平台证书是否已更新——平台证书有效期通常是五年,但更换商户证书后旧的平台证书就不能再验签了。
最后一个实用技巧:回调接口的密码学操作集中到一个PayNotifyService,所有渠道的验签、解密、幂等判断都收敛在这一个类里,每个渠道只暴露parseParams()和verify(params)两个方法。这样换支付渠道时,业务层只依赖统一的通知结果对象,不会出现「微信下单、支付宝回调」这种剪不断理还乱的后端代码。
本文还有配套的精品资源,点击获取