简介:Xpay-3.1版全开源无授权免签约支付源码,面向Java Web开发者及中小型技术团队,提供可商用、可深度定制的轻量级支付系统解决方案,有效降低接入第三方支付的开发门槛与合规成本。资源包共823个文件,涵盖108个JS前端交互脚本、47个CSS样式文件、36个Java后端核心类、37个HTML页面模板,以及PNG图标、DOCX文档、SQL配置、Redis与MySQL部署说明等,完整呈现支付网关、订单管理、多渠道(支付宝/微信/QQ)对接、安全加密与退款流程等模块实现,压缩包仅16.5MB,结构清晰便于快速定位与二次开发。目前已有129人学习下载,资源附带Swagger接口文档、_xpay-code项目根目录标识、_所需环境或中间件说明及Windows/Linux双平台运行提示,开箱即用,适合教学演示、创业项目原型搭建或企业内部支付模块快速集成。
1. Xpay-3.1 不是“免签即用”的黑盒,而是需深度理解支付网关抽象层的开源系统
很多人下载 Xpay-3.1 版源码后第一反应是:“全开源、免签约,是不是解压就能收钱?”——实际恰恰相反。Xpay 的核心价值不在“跳过签约”,而在于它把微信支付、支付宝等主流通道的异步通知验签、订单状态机、支付结果轮询、退款幂等控制、商户密钥隔离等共性逻辑,封装成可插拔的抽象层。所谓“免签约”,是指不强制绑定某家 SaaS 支付服务商,但你仍需自行申请微信/支付宝的商户号,并配置合法的 APIv3 密钥、证书与回调地址。它面向的是已有支付资质、需要自主掌控资金流与风控策略的中小技术团队,而非零基础个体户。如果你正被第三方支付 SDK 的硬编码耦合、回调验签失败率高、退款状态不同步等问题困扰,Xpay-3.1 提供的是一套可审计、可调试、可灰度发布的支付中间件骨架,而不是一个开箱即用的收款页面。
2. 拆解 Xpay-3.1 的支付通道抽象模型:为什么必须重写PayChannel接口实现
Xpay-3.1 的架构设计围绕PayChannel接口展开,它定义了支付网关最核心的 5 个契约方法:createOrder()创建预支付交易、queryOrder()主动查单、refund()发起退款、closeOrder()关闭未支付订单、notifyHandler()处理平台异步回调。这并非简单封装 HTTP 请求,而是对支付生命周期的语义建模。例如createOrder()返回的不是原始 JSON 响应体,而是一个标准化的PayResponse对象,其中payUrl字段在微信 JSAPI 场景下是wx://协议链接,在支付宝扫码场景下是https://二维码地址,上层业务无需感知渠道差异。
2.1 微信支付 V3 接入的关键三步:证书加载、签名生成、响应解析
微信支付 V3 要求所有请求必须携带Authorization头,其值由商户私钥对请求路径、时间戳、随机字符串、请求体 SHA256 哈希进行 RSA-SHA256 签名。Xpay-3.1 在WechatPayChannel.java中通过WechatPayHttpClient封装该流程:
// src/main/java/com/xpay/channel/wechat/WechatPayChannel.java public PayResponse createOrder(PayOrder order) { // 1. 构造请求体(省略字段校验) WechatPayRequest req = new WechatPayRequest(); req.setAppid(config.getAppId()); req.setMchid(config.getMchId()); req.setDescription(order.getSubject()); req.setOutTradeNo(order.getTradeNo()); req.setAmount(new Amount().setTotal(order.getAmount()).setCurrency("CNY")); // 2. 使用 Xpay 内置的 HttpClient 自动添加 Authorization 头 String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"; HttpResponse resp = httpClient.post(url, req.toJson(), config); // 3. 解析响应并映射为统一 PayResponse WechatPayResponse wxResp = JsonUtil.fromJson(resp.getBody(), WechatPayResponse.class); return PayResponse.builder() .payUrl(wxResp.getPrepayId()) // 注意:此处返回 prepay_id,前端需二次调用微信 JSAPI .tradeNo(order.getTradeNo()) .build(); }提示:
httpClient.post()方法内部会自动读取config.getCertPath()加载商户证书,并调用Signer.sign()生成符合微信规范的签名头。若证书路径错误或私钥密码不匹配,日志中会出现java.security.SignatureException: invalid signature,此时需检查xpay.yml中wechat.cert-path和wechat.cert-password是否指向 PEM 格式证书及正确密码。
2.2 支付宝沙箱环境联调:如何绕过公钥证书验证陷阱
支付宝开放平台沙箱环境要求使用alipay.public.key进行回调验签,但其公钥格式与生产环境不同,且沙箱公钥需从开发者后台手动下载。Xpay-3.1 在AlipayChannel.java中通过AlipaySignature.rsaCheckV1()执行验签,但默认配置易踩两个坑:
- 第一,
alipay.public.key必须是PKCS#8 格式(以-----BEGIN PUBLIC KEY-----开头),而非 PKCS#1(-----BEGIN RSA PUBLIC KEY-----); - 第二,沙箱回调 URL 必须与
xpay.yml中alipay.notify-url完全一致(含末尾/),否则支付宝拒绝发送通知。
以下为沙箱环境最小化配置示例(xpay.yml):
alipay: app-id: 2021000123456789 merchant-private-key: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC..." alipay-public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuJZ..." notify-url: "https://your-domain.com/api/alipay/notify/" sandbox: true注意:
alipay-public-key字段值需粘贴沙箱公钥内容(去除换行符),且不能包含任何空格或不可见字符。建议用cat alipay_public_key.pem | tr -d '\n' | sed 's/-----.*-----//g'命令清洗后填入配置。
3. 部署前必做的 4 项安全加固:从密钥管理到回调防护
Xpay-3.1 作为直连支付通道的后端服务,其安全性直接关系到资金安全。开源不等于无责,以下加固措施在application-prod.yml中必须显式配置,不可依赖默认值。
3.1 商户密钥必须脱离代码库:使用 Spring Cloud Config 或环境变量注入
Xpay-3.1 默认将微信/支付宝密钥写在xpay.yml中,这是严重安全隐患。正确做法是通过 Spring Boot 的@ConfigurationProperties绑定机制,从环境变量读取敏感字段:
// src/main/java/com/xpay/config/PayConfig.java @ConfigurationProperties(prefix = "xpay.pay") @Data public class PayConfig { private Wechat wechat = new Wechat(); private Alipay alipay = new Alipay(); @Data public static class Wechat { private String appId; private String mchId; private String apiKey; // 对应环境变量 XPAY_PAY_WECHAT_APIKEY private String certPath; // 对应 XPAY_PAY_WECHAT_CERTPATH private String certPassword; // 对应 XPAY_PAY_WECHAT_CERTPASSWORD } }启动时传入环境变量:
java -jar xpay-3.1.jar \ --XPAY_PAY_WECHAT_APIKEY="your-wechat-apikey" \ --XPAY_PAY_WECHAT_CERTPATH="/etc/certs/wechat/apiclient_cert.pem"3.2 回调接口必须启用 IP 白名单与请求体完整性校验
微信/支付宝回调虽带签名,但攻击者可伪造请求头绕过签名验证。Xpay-3.1 在NotifyController.java中提供@ValidIpRange注解,需配合 Nginx 层做双重防护:
# nginx.conf 片段:仅允许微信官方 IP 段访问 notify 接口 location /api/wechat/notify/ { allow 182.254.0.0/16; allow 182.254.128.0/17; allow 202.127.0.0/16; deny all; proxy_pass http://xpay-backend; }同时在xpay.yml中开启请求体哈希校验(防止中间人篡改 body):
xpay: notify: verify-body-hash: true # 启用 SHA256(body) 与 header 中 x-wx-hash 比对 timeout-millis: 5000提示:微信回调请求头中
x-wx-hash字段为 Base64 编码的请求体 SHA256 值,Xpay-3.1 会在WechatNotifyHandler中自动提取并比对。若比对失败,返回 HTTP 400 并记录INVALID_BODY_HASH错误码。
3.3 订单状态机必须禁用外部直接修改:OrderService.updateStatus()的幂等锁
Xpay-3.1 使用数据库乐观锁控制订单状态流转,避免并发退款导致资损。关键逻辑在OrderService.java的updateStatusIfMatch()方法:
// 更新订单状态,仅当当前状态为 expectStatus 时才执行 public int updateStatusIfMatch(String tradeNo, OrderStatus expectStatus, OrderStatus targetStatus) { return orderMapper.update( new UpdateWrapper<OrderEntity>() .eq("trade_no", tradeNo) .eq("status", expectStatus.getCode()) // 断言当前状态 .set("status", targetStatus.getCode()) .set("update_time", LocalDateTime.now()) ); }该方法返回影响行数,若为 0 表示状态已变更(如用户重复点击退款按钮),此时应抛出BusinessException("ORDER_STATUS_MISMATCH")而非静默失败。
3.4 日志脱敏:支付敏感字段必须被@Sensitive注解拦截
Xpay-3.1 内置SensitiveLogFilter,对标注@Sensitive的字段自动替换为***。需在PayOrder实体类中明确标记:
public class PayOrder { private String tradeNo; // 交易号,可明文 private String subject; // 商品标题,可明文 @Sensitive // 标记为敏感字段 private String payerOpenId; // 用户 openid,必须脱敏 @Sensitive private String attach; // 附加参数,可能含用户 ID }启动时需启用日志过滤器:
xpay: log: sensitive-enabled: true4. 生产环境高频问题排错:从验签失败到订单超时的 5 类根因定位法
上线后最常见的 5 类问题,均能在 Xpay-3.1 的日志结构中快速定位根因。以下为标准排查路径,按日志关键字顺序展开。
4.1 验签失败:VERIFY_SIGN_FAILED错误的三层定位
当WechatNotifyHandler或AlipayNotifyHandler报VERIFY_SIGN_FAILED,按以下顺序检查:
| 日志关键字 | 检查项 | 命令/操作 |
|---|---|---|
Invalid signature | 微信回调签名头缺失或格式错误 | curl -v https://your-domain.com/api/wechat/notify/查看响应头是否含x-wx-signature |
Certificate not found | 商户证书未加载成功 | ls -l $(readlink -f $CERT_PATH)确认文件存在且 Java 进程有读取权限 |
Signature does not match | 请求体被 Tomcat 或 Nginx 修改(如 gzip、transfer-encoding) | 在application.yml中设置 `server.tomcat.relaxed-query-chars= |
4.2 订单查不到:QUERY_ORDER_NOT_FOUND的数据库索引优化
queryOrder()接口响应慢或查不到记录,90% 源于order_trade_no字段未建索引。执行以下 SQL 确认:
-- 检查索引是否存在 SHOW INDEX FROM t_pay_order WHERE Key_name = 'idx_trade_no'; -- 若不存在,立即添加(MySQL) ALTER TABLE t_pay_order ADD INDEX idx_trade_no (trade_no) COMMENT '支付订单号查询索引';Xpay-3.1 的OrderMapper.xml中selectByTradeNo查询使用trade_no作为唯一条件,无索引时全表扫描会导致超时。
4.3 退款失败:REFUND_AMOUNT_MISMATCH的金额精度陷阱
微信/支付宝退款金额必须为整数分,且不能超过原订单金额。Xpay-3.1 在RefundService.java中强制校验:
if (refundAmount > order.getAmount()) { throw new BusinessException("REFUND_AMOUNT_MISMATCH"); } if (refundAmount % 100 != 0) { // 必须为整数分 throw new BusinessException("REFUND_AMOUNT_PRECISION_ERROR"); }注意:前端传入的
refundAmount单位必须是“分”,而非“元”。若前端 JavaScript 传递19.99(元),后端需乘以 100 转为1999(分)再校验,否则触发精度错误。
4.4 通知重复:NOTIFY_DUPLICATE_RECEIVED的防重表设计
支付宝回调存在极小概率重复推送,Xpay-3.1 使用t_notify_record表记录notify_id(支付宝的notify_id参数)去重。若该表未创建或notify_id字段无唯一索引,将导致重复处理。建表语句如下:
CREATE TABLE `t_notify_record` ( `id` bigint NOT NULL AUTO_INCREMENT, `notify_id` varchar(64) NOT NULL COMMENT '支付宝/微信通知唯一ID', `channel` varchar(16) NOT NULL COMMENT 'weixin/alipay', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_notify_id` (`notify_id`) -- 关键:必须唯一索引 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;4.5 本地调试:用MockNotifyServer模拟微信回调的完整链路
开发阶段无法触发真实微信回调,Xpay-3.1 提供MockNotifyServer.java工具类,可一键启动内嵌 HTTP 服务模拟通知:
// 启动模拟服务器(端口 8081) MockNotifyServer server = new MockNotifyServer(8081); server.start(); // 构造微信回调请求体(JSON) String body = "{...}"; // 从微信文档复制示例 String signature = WechatSigner.sign(body, config); // 使用你的密钥签名 // 发送 POST 请求 HttpResponse resp = HttpUtil.post("http://localhost:8081/api/wechat/notify/", body, Map.of("Wechatpay-Signature", signature)); System.out.println("Mock response: " + resp.getStatus());该工具会自动加载xpay.yml配置,调用真实的WechatNotifyHandler,便于断点调试验签、状态更新全流程。
5. 进阶技巧:用 Xpay-3.1 的PayPlugin机制接入自定义支付通道
Xpay-3.1 的扩展性体现在PayPlugin接口,它允许你以 JAR 包形式注入新支付通道,无需修改主工程代码。例如接入某地方银行的聚合支付网关,只需实现以下三个类:
5.1 定义插件元信息:BankPayPlugin.java
@Component public class BankPayPlugin implements PayPlugin { @Override public String getChannelCode() { return "bankpay"; // 通道编码,需全局唯一 } @Override public PayChannel createChannel(PayConfig config) { return new BankPayChannel(config); } @Override public void validateConfig(PayConfig config) throws ValidationException { if (StringUtils.isBlank(config.getBankpay().getGatewayUrl())) { throw new ValidationException("bankpay.gateway-url is required"); } } }5.2 实现通道逻辑:BankPayChannel.java
继承AbstractPayChannel复用通用能力(如日志、监控、重试):
public class BankPayChannel extends AbstractPayChannel { public BankPayChannel(PayConfig config) { super(config); } @Override public PayResponse createOrder(PayOrder order) { // 调用银行网关 REST API String url = config.getBankpay().getGatewayUrl() + "/pay"; Map<String, Object> params = buildBankPayParams(order); String respBody = httpClient.postJson(url, params, config.getBankpay().getApiKey()); return parseBankResponse(respBody); } }5.3 插件打包与加载:pom.xml依赖隔离
插件模块的pom.xml必须声明provided作用域,避免与主工程冲突:
<dependency> <groupId>com.xpay</groupId> <artifactId>xpay-core</artifactId> <version>3.1</version> <scope>provided</scope> <!-- 关键:不打入插件 JAR --> </dependency>编译后将bankpay-plugin-1.0.jar放入主工程plugins/目录,Xpay-3.1 启动时自动扫描并注册BankPayPlugin。此时xpay.yml中可配置:
bankpay: gateway-url: "https://gateway.bank.com/api" api-key: "your-bank-apikey"提示:插件类加载由
PluginClassLoader管理,确保插件 JAR 不包含spring-boot-starter-web等与主工程冲突的依赖。可通过jar -tf bankpay-plugin-1.0.jar | grep starter快速检查。
本文还有配套的精品资源,点击获取