重要提示:JSP 和微信支付政策
在开始之前,必须强调一个关键点:

(图片来源网络,侵删)
微信官方早已不再推荐甚至限制在纯 HTML/JSP 页面中直接调起微信支付。
- 原因:微信支付的安全校验是基于微信浏览器的特定 JS-SDK (
jssdk),它要求页面必须在微信内置浏览器中打开,纯 HTML/JSP 页面没有后端能力来获取appId,timestamp,nonceStr,signature等必要参数来调用 JS-SDK。 - 官方推荐方案:微信官方推荐的 H5 支付,其流程是:
- 用户在手机浏览器或微信内访问你的网页。
- 你的后端服务器判断用户环境(是否在微信内)。
- 如果在微信内,生成一个支付 URL。
- 你的网页前端(使用 JavaScript)将这个 URL 重定向到微信客户端,微信客户端会自动拉起支付。
- 如果不在微信内,则跳转到普通扫码支付页面。
下面我将为您提供一个 “最接近 JSP 开发者习惯”的解决方案,它本质上是一个 模拟的、用于演示的 JSP Demo,在实际生产环境中,请务必遵循微信官方的 H5 支付流程。
第一步:准备工作(在微信商户平台配置)
在写任何代码之前,你必须在微信支付商户平台完成以下配置:
- 申请微信支付商户号:拥有一个审核通过的商户号。
- 获取关键参数:
- APIv3 密钥 (APIv3 Key):在【账户中心】->【API安全】->【APIv3密钥】中设置和获取,这是签名和加密解密的核心。
- API 证书:下载商户平台提供的 API 证书(包括证书和私钥),在后续代码中,私钥用于签名,证书用于验证微信的响应。
- AppID:你的公众号或小程序的唯一标识。
- 商户号 (mch_id):你的商户号。
- 产品开通:确保在【产品中心】已经开通了【JSAPI 支付】或【H5 支付】产品。
- 域名配置:在【产品中心】->【开发配置】->【支付授权目录 中,配置你的网站域名,如果你的支付页面是
http://www.yourdomain.com/pay.jsp,那么你需要添加http://www.yourdomain.com/到白名单中。(注意:对于 H5 支付,配置的是支付授权域名)**
第二步:创建项目环境
我们使用一个简单的 Java Web 项目来演示。

(图片来源网络,侵删)
-
创建 Web 项目:在 Eclipse 或 IntelliJ IDEA 中创建一个 Dynamic Web Project。
-
添加依赖库:你需要引入微信支付官方提供的 SDK 和 JSON 处理库。
- 微信支付 SDK:从 微信支付官方 GitHub 下载最新版本的 JAR 包,或者使用 Maven。
- JSON 库:推荐使用
fastjson或gson,这里以fastjson为例。
Maven 依赖 (
pom.xml):<dependencies> <!-- 微信支付 V3 SDK --> <dependency> <groupId>com.wechatpay.v3</groupId> <artifactId>wechatpay-java</artifactId> <version>0.2.11</version> </dependency> <!-- 阿里巴巴 fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.83</version> </dependency> <!-- Servlet API --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> </dependencies>
第三步:后端 Java 代码实现
后端主要负责两个核心功能:

(图片来源网络,侵删)
- 创建支付订单:调用微信统一下单接口,生成预支付交易会话标识 (
prepay_id)。 - 生成前端调起支付所需参数:根据
prepay_id和你的 AppID 等信息,生成签名后的参数,供前端 JavaScript 使用。
配置信息类 (WeChatConfig.java)
创建一个类来存储你的微信支付配置信息,方便管理。
public class WeChatConfig {
public static final String APP_ID = "你的AppID"; // 替换为你的AppID
public static final String MCH_ID = "你的商户号"; // 替换为你的商户号
public static final String API_V3_KEY = "你的APIv3密钥"; // 替换为你的APIv3密钥
public static final String NOTIFY_URL = "http://www.yourdomain.com/wechatpay/notify.jsp"; // 支付结果通知URL
public static final String API_KEY_PATH = "D:/cert/apiclient_key.pem"; // 替换为你的私钥文件路径
}
支付服务工具类 (WeChatPayService.java)
这是核心逻辑,负责与微信服务器通信。
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wechatpay.v3.WechatPayHttpClientBuilder;
import com.wechatpay.v3.auth.CertificatesVerifier;
import com.wechatpay.v3.auth.PrivateKeySigner;
import com.wechatpay.v3.auth.WechatPay2Credentials;
import com.wechatpay.v3.core.Config;
import com.wechatpay.v3.core.RSASSASigner;
import com.wechatpay.v3.core.exception.MalformedMessageException;
import com.wechatpay.v3.core.exception.ServiceException;
import com.wechatpay.v3.core.util.PemUtil;
import com.wechatpay.v3.jsapi.model.Amount;
import com.wechatpay.v3.jsapi.model.Payer;
import com.wechatpay.v3.jsapi.model.PrepayRequest;
import com.wechatpay.v3.jsapi.model.PrepayResponse;
import com.wechatpay.v3.jsapi.service.JsapiService;
import com.wechatpay.v3.util.AesUtil;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class WeChatPayService {
private final JsapiService service;
public WeChatPayService() {
// 1. 加载私钥
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(
Files.readAllBytes(Paths.get(WeChatConfig.API_KEY_PATH))));
// 2. 初始化签名器
Config config = new Config.Builder()
.merchantId(WeChatConfig.MCH_ID)
.privateKey(merchantPrivateKey)
.wxpayCertificate(new CertificatesVerifier(new WechatPay2Credentials(WeChatConfig.MCH_ID, new PrivateKeySigner(WeChatConfig.MCH_ID, merchantPrivateKey))))
.build();
RSASSASigner signer = new RSASSASigner(config);
// 3. 初始化服务
this.service = new JsapiService.Builder()
.credentials(new WechatPay2Credentials(WeChatConfig.MCH_ID, new PrivateKeySigner(WeChatConfig.MCH_ID, merchantPrivateKey)))
.signer(signer)
.build();
}
/**
* 创建预支付订单
* @param openid 用户的openid (必须通过微信授权获取)
* @param outTradeNo 商户订单号
* @param totalFee 订单金额,单位:分
* @return 返回前端调起支付所需的参数
*/
public Map<String, String> createPrepayId(String openid, String outTradeNo, Integer totalFee) throws Exception {
// 1. 构造请求body
PrepayRequest prepayRequest = new PrepayRequest();
prepayRequest.setAppid(WeChatConfig.APP_ID);
prepayRequest.setMchid(WeChatConfig.MCH_ID);
prepayRequest.setDescription("商品描述"); // 商品描述
prepayRequest.setNotifyUrl(WeChatConfig.NOTIFY_URL);
prepayRequest.setOutTradeNo(outTradeNo);
Amount amount = new Amount();
amount.setTotal(totalFee); // 金额,单位:分
prepayRequest.setAmount(amount);
Payer payer = new Payer();
payer.setOpenid(openid); // 必须是用户的openid
prepayRequest.setPayer(payer);
// 2. 调用统一下单API
PrepayResponse prepayResponse = service.prepay(prepayRequest);
// 3. 获取prepay_id
String prepayId = prepayResponse.getPrepayId();
// 4. 生成前端调起支付所需的参数
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonceStr = UUID.randomUUID().toString().replace("-", "");
Map<String, String> params = new HashMap<>();
params.put("appId", WeChatConfig.APP_ID);
params.put("timeStamp", timeStamp);
params.put("nonceStr", nonceStr);
params.put("package", "prepay_id=" + prepayId);
params.put("signType", "RSA");
// 5. 生成签名 (这里SDK内部处理了,我们按格式返回即可)
// 注意:JSAPI调起支付的签名方式是固定的,不是对整个params签名,而是对appId, timeStamp, nonceStr, package进行签名
// 微信JS-SDK会自己处理签名,我们只需要把上述参数按顺序传给它即可。
// 但为了安全,最好按照微信文档再次生成一个签名并返回给前端,让前端也带上。
// 在JSP Demo的简化场景下,我们通常只返回这些参数,相信微信JS-SDK。
// 如果要自己生成签名,可以这样做:
// String paySign = Signer.sign(params, merchantPrivateKey); // 需要一个签名工具类
// params.put("paySign", paySign);
return params;
}
}
Servlet 处理支付请求 (PayServlet.java)
创建一个 Servlet 来接收前端的支付请求,并调用上面的服务。
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
@WebServlet("/pay")
public class PayServlet extends HttpServlet {
private WeChatPayService weChatPayService;
@Override
public void init() throws ServletException {
weChatPayService = new WeChatPayService();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 获取前端传来的参数
String openid = request.getParameter("openid"); // **重要**:在实际应用中,openid需要通过微信OAuth2.0授权获取
String outTradeNo = "ORDER_" + System.currentTimeMillis(); // 生成商户订单号
Integer totalFee = 1; // 订单金额,单位:分,这里是1分钱
try {
// 2. 调用服务创建预支付订单
Map<String, String> payParams = weChatPayService.createPrepayId(openid, outTradeNo, totalFee);
// 3. 将参数以JSON格式返回给前端
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(JSON.toJSONString(payParams));
} catch (Exception e) {
e.printStackTrace();
// 发生错误时返回错误信息
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write("{\"code\": 500, \"message\": \"支付请求失败: " + e.getMessage() + "\"}");
}
}
}
第四步:前端 JSP 页面实现
现在我们创建一个 JSP 页面,它有两个作用:
- 显示一个“支付”按钮。
- 当用户点击按钮时,向后端 Servlet 发送请求获取支付参数,并尝试调起微信支付。
pay.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>微信支付 JSP Demo</title>
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<script>
// **注意**:这个demo只能在微信内置浏览器中运行!
// 因为wx.chooseWXPay是微信JS-SDK的方法,只在微信环境中存在。
function onBridgeReady() {
// 1. 发起AJAX请求到后端Servlet,获取支付参数
var xhr = new XMLHttpRequest();
xhr.open("POST", "${pageContext.request.contextPath}/pay", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
// **重要**:这里的openid是硬编码的,实际开发中需要通过微信OAuth2.0流程获取
xhr.send("openid=oUpF8uMuAJO_M2pxb1Q9zNjWeS6o");
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var response = JSON.parse(xhr.responseText);
console.log("从后端获取的支付参数:", response);
// 2. 调用微信JS-SDK的支付接口
wx.chooseWXPay({
appId: response.appId, // 公众号ID,由商户传入
timeStamp: response.timeStamp, // 时间戳,自1970年以来的秒数
nonceStr: response.nonceStr, // 随机串
package: response.package, // 统一下单接口返回的 prepay_id 参数值
signType: response.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: response.paySign, // 支付签名
success: function (res) {
// 支付成功后的回调函数
alert("支付成功!");
console.log("支付成功回调:", res);
},
cancel: function (res) {
// 用户取消支付
alert("您已取消支付。");
console.log("支付取消回调:", res);
},
fail: function (res) {
// 支付失败
alert("支付失败,请重试。");
console.log("支付失败回调:", res);
}
});
} else if (xhr.readyState === 4 && xhr.status !== 200) {
alert("服务器错误,无法获取支付参数。");
console.error("获取支付参数失败:", xhr.statusText);
}
};
}
// 3. 判断微信JS-SDK是否加载完成
if (typeof wx === 'undefined') {
alert("请在微信内打开此页面!");
} else {
// 由于JS-SDK加载是异步的,我们直接调用,内部会处理
// 更严谨的做法是监听 `WeixinJSBridgeReady` 事件
document.addEventListener('WeixinJSBridgeReady', function() {
onBridgeReady();
}, false);
// 兼容旧版本
if (typeof WeixinJSBridge !== "undefined") {
onBridgeReady();
}
}
</script>
</head>
<body>
<h1>微信支付 JSP Demo</h1>
<p>请确保在微信内置浏览器中打开此页面。</p>
<p>页面加载后会自动尝试调起支付。</p>
</body>
</html>
第五步:支付结果通知(异步回调)
当用户支付成功或失败后,微信服务器会主动向你配置的 NOTIFY_URL 发送一个 POST 请求,你的服务器需要处理这个请求,并返回特定的应答。
notify.jsp
<%@ page import="com.wechatpay.v3.util.AesUtil" %>
<%@ page import="com.alibaba.fastjson.JSON" %>
<%@ page import="com.alibaba.fastjson.JSONObject" %>
<%@ page import="java.nio.charset.StandardCharsets" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.security.PrivateKey" %>
<%@ page import="java.nio.file.Files" %>
<%@ page import="java.nio.file.Paths" %>
<%@ page import="com.wechatpay.v3.util.PemUtil" %>
<%@ page import="com.wechatpay.v3.auth.CertificatesVerifier" %>
<%@ page import="com.wechatpay.v3.auth.WechatPay2Credentials" %>
<%@ page import="com.wechatpay.v3.auth.PrivateKeySigner" %>
<%@ page import="com.wechatpay.v3.core.RSASSASigner" %>
<%@ page import="com.wechatpay.v3.core.Config" %>
<%@ page import="com.wechatpay.v3.notification.Notification" %>
<%@ page import="com.wechatpay.v3.notification.NotificationParser" %>
<%@ page import="com.wechatpay.v3.notification.EventHandler" %>
<%@ page contentType="text/xml;charset=UTF-8" language="java" %>
<%
// 1. 读取微信发送的XML报文
BufferedReader reader = request.getReader();
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String body = sb.toString();
System.out.println("收到微信支付通知: " + body);
try {
// 2. 初始化SDK和验证器
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(
Files.readAllBytes(Paths.get(WeChatConfig.API_KEY_PATH))));
Config config = new Config.Builder()
.merchantId(WeChatConfig.MCH_ID)
.privateKey(merchantPrivateKey)
.wxpayCertificate(new CertificatesVerifier(new WechatPay2Credentials(WeChatConfig.MCH_ID, new PrivateKeySigner(WeChatConfig.MCH_ID, merchantPrivateKey))))
.build();
RSASSASigner signer = new RSASSASigner(config);
// 3. 解析通知
NotificationParser parser = new NotificationParser(signer);
Notification notification = parser.parse(body, WeChatConfig.API_V3_KEY.getBytes(StandardCharsets.UTF_8));
// 4. 处理通知事件
if ("TRANSACTION.SUCCESS".equals(notification.getEventType())) {
// 业务逻辑:更新订单状态为“已支付”
JSONObject resourceData = JSON.parseObject(new String(notification.getResource().getAssociatedData(), StandardCharsets.UTF_8));
String outTradeNo = resourceData.getString("out_trade_no");
String transactionId = resourceData.getString("transaction_id");
System.out.println("订单支付成功!");
System.out.println("商户订单号: " + outTradeNo);
System.out.println("微信支付订单号: " + transactionId);
// TODO: 在这里更新你的数据库订单状态
// orderService.paySuccess(outTradeNo, transactionId);
// 5. 返回成功应答给微信服务器
out.println("<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
} else {
// 其他事件类型,如TRANSACTION.FAILED
System.out.println("收到非支付成功通知: " + notification.getEventType());
out.println("<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[Unknown Event]]></return_msg></xml>");
}
} catch (Exception e) {
e.printStackTrace();
// 验证失败或处理异常,返回失败应答
out.println("<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[FAIL]]></return_msg></xml>");
}
%>
总结与部署
- 这个 JSP Demo 展示了一个完整的后端生成支付参数、前端调起支付的流程,但请务必记住,这只是一个模拟和教学示例,真正的生产环境应使用微信官方推荐的 H5 支付流程,即后端生成一个支付 URL,前端通过
window.location.href或WeixinJSBridge.invoke的方式跳转。 - 部署:
- 将项目打包成 WAR 文件。
- 部署到 Tomcat 或其他 Java Web 服务器上。
- 确保服务器的 IP 和域名已经在微信商户平台的白名单中。
- 用微信扫描二维码,在手机微信中访问
http://你的服务器IP:端口/pay.jsp,测试支付流程。
这个 Demo 覆盖了从后端到前端的核心逻辑,并附带了必要的注释和注意事项,希望能帮助你理解微信网页支付的实现原理。
