-
一、准备开发所需的账号以及配置信息前往https://mp.weixin.qq.com/ (微信公众平台)注册一个应用,类型只能是:公众号/小程序/企业微信,订阅号不支持微信支付接口,注册完成需要完成”微信认证“
-
前往:https://pay.weixin.qq.com(微信支付商户平台)注册一个商户,支付成功后的钱就会在这个账号里面
-
上面两个平台注册完成之后就需要把配置信息拿到了:1、APPID:应用id也就是 公众号/小程序的ID 2、key: 应用密钥。mch_Id:商户ID (收钱的商家ID)
-
然后就进入我们的开发过程,在Maven中创建一个Spring Boot项目,添加所需的依赖(例如zxing等二维码生成库)。
-
<dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.4.0</version> </dependency>
-
在resources/static目录下创建index.html页面,添加生成二维码的表单,让用户输入订单金额等信息,并提交生成二维码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>微信支付</title>
</head>
<body>
<form action="" method="post" id="form">
<label for="totalFee">订单金额:</label>
<input type="number" id="totalFee" name="totalFee" min="0.01" step="0.01" required>
<button type="submit">生成二维码</button>
</form>
<div id="qrcode"></div>
<script type="text/javascript">
var form = document.getElementById("form");
form.addEventListener("submit", function(e) {
e.preventDefault();
var xhr = new XMLHttpRequest();
xhr.open("get", "generateQRCode?totalFee=" + document.getElementById("totalFee").value);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
var response = JSON.parse(xhr.responseText);
document.getElementById("qrcode").innerHTML = "<img src=\"" + response.qrcodeUrl + "\" alt=\"\">";
} else {
alert("生成二维码失败");
}
}
};
xhr.send();
});
</script>
</body>
</html>
- 创建PaymentController类,处理用户提交的表单,生成二维码图片,并将付款链接和二维码图片发送到页面:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestParam;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
@RestController
public class PaymentController {
private static final String APP_ID = "xxxx"; // 公众账号ID
private static final String MCH_ID = "xxxx"; // 商户号
private static final String NOTIFY_URL = "http://xxxx.com/notify"; // 异步通知地址
private static final String KEY = "xxxx"; // 商户支付密钥
private static final String UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; // 统一下单接口地址
@GetMapping("/")
public String index() {
return "index";
}
@GetMapping("/generateQRCode")
public Map<String, String> generateQRCode(@RequestParam("totalFee") String totalFee) throws Exception {
String outTradeNo = "order" + System.currentTimeMillis(); // 商户订单号,可根据实际情况生成
String nonceStr = getRandomString();
String body = "购买商品";
String spbillCreateIp = "127.0.0.1"; // 客户端IP,可自行修改
String tradeType = "NATIVE";
String productId = "product" + System.currentTimeMillis(); // 商品ID,可根据实际情况生成
String attach = "附加数据";
String sign = getSign(APP_ID, MCH_ID, nonceStr, body, attach, outTradeNo, totalFee, spbillCreateIp, NOTIFY_URL, tradeType,
productId, KEY);
String xml = getXml(APP_ID, MCH_ID, nonceStr, body, attach, outTradeNo, totalFee, spbillCreateIp, NOTIFY_URL,
tradeType, productId, sign);
String responseXml = doPost(UNIFIEDORDER_URL, xml);
String prepayId = getPrepayId(responseXml);
if (prepayId == null) { // 如果预支付交易会话标识prepay_id为空,则失败
Map<String, String> map = new HashMap<>();
map.put("qrcodeUrl", "");
map.put("codeUrl", "");
return map;
}
String codeUrl = getCodeUrl(prepayId);
String qrcodeUrl = generateQRCode(codeUrl); // 生成二维码图片,并返回图片的URL
Map<String, String> map = new HashMap<>();
map.put("qrcodeUrl", qrcodeUrl);
map.put("codeUrl", codeUrl);
return map;
}
private String getSign(String appId, String mchId, String nonceStr, String body, String attach, String outTradeNo,
String totalFee, String spbillCreateIp, String notifyUrl, String tradeType, String productId, String key) {
Map<String, String> params = new HashMap<String, String>();
params.put("appid", appId);
params.put("mch_id", mchId);
params.put("nonce_str", nonceStr);
params.put("body", body);
params.put("attach", attach);
params.put("out_trade_no", outTradeNo);
params.put("total_fee", totalFee);
params.put("spbill_create_ip", spbillCreateIp);
params.put("notify_url", notifyUrl);
params.put("trade_type", tradeType);
params.put("product_id", productId);
String sign = getSign(params, key);
return sign;
}
private String getSign(Map<String, String> params, String key) {
StringBuilder sb = new StringBuilder();
for (String k : params.keySet()) {
if ("sign".equals(k)) {
continue;
}
String v = params.get(k);
if (v != null && v.length() > 0) {
sb.append(k).append("=").append(v).append("&");
}
}
sb.append("key=").append(key);
String sign = md5(sb.toString()).toUpperCase();
return sign;
}
private String getXml(String appId, String mchId, String nonceStr, String body, String attach, String outTradeNo,
String totalFee, String spbillCreateIp, String notifyUrl, String tradeType, String productId, String sign) {
StringBuilder sb = new StringBuilder();
sb.append("<xml>");
sb.append("<appid>").append(appId).append("</appid>");
sb.append("<mch_id>").append(mchId).append("</mch_id>");
sb.append("<nonce_str>").append(nonceStr).append("</nonce_str>");
sb.append("<body><![CDATA[").append(body).append("]]></body>");
sb.append("<attach>").append(attach).append("</attach>");
sb.append("<out_trade_no>").append(outTradeNo).append("</out_trade_no>");
sb.append("<total_fee>").append(totalFee).append("</total_fee>");
sb.append("<spbill_create_ip>").append(spbillCreateIp).append("</spbill_create_ip>");
sb.append("<notify_url>").append(notifyUrl).append("</notify_url>");
sb.append("<trade_type>").append(tradeType).append("</trade_type>");
sb.append("<product_id>").append(productId).append("</product_id>");
sb.append("<sign>").append(sign).append("</sign>");
sb.append("</xml>");
return sb.toString();
}
private String getPrepayId(String xml) {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
InputStream is = org.apache.commons.io.IOUtils.toInputStream(xml, "UTF-8");;
Document doc = db.parse(is);
NodeList nodeList = doc.getElementsByTagName("prepay_id");
Node node = nodeList.item(0);
if (node == null) {
return null;
}
return node.getTextContent();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private String getCodeUrl(String prepayId) {
if (prepayId == null || prepayId.length() == 0) {
return null;
}
String codeUrl = "weixin://wxpay/bizpayurl?pr=" + prepayId;
return codeUrl;
}
private String generateQRCode(String text) throws IOException, WriterException {
int width = 300;
int height = 300;
QRCodeWriter writer = new QRCodeWriter();
BitMatrix matrix = writer.encode(text, BarcodeFormat.QR_CODE, width, height);
ByteArrayOutputStream os = new ByteArrayOutputStream();
com.google.zxing.client.j2se.MatrixToImageWriter.writeToStream(matrix, "png", os);
byte[] bytes = os.toByteArray();
os.close();
String qrcodeUrl = "data:image/png;base64," + Base64.getEncoder().encodeToString(bytes);
return qrcodeUrl;
}
()方法,用于生成随机字符串:private String getRandomString
private String getRandomString() {
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 32; i++) {
int index = random.nextInt(str.length());
sb.append(str.charAt(index));
}
return sb.toString();
}
- 创建NotifyController类,处理微信支付异步通知:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class NotifyController {
private static final String KEY = "xxxx"; // 商户支付密钥
@PostMapping("/notify")
public String notify(HttpServletRequest request) throws Exception {
// 读取请求内容
InputStream inputStream = request.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
reader.close();
inputStream.close();
String xml = sb.toString();
// 验证签名
Map<String, String> params = new HashMap<String, String>();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
InputStream is = new ByteArrayInputStream(xml.getBytes("UTF-8"));
Document doc = db.parse(is);
NodeList nodeList = doc.getFirstChild().getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
String key = node.getNodeName();
String value = node.getTextContent();
params.put(key, value);
}
}
String sign = getSign(params, KEY);
String signFromXml = params.get("sign");
if (!sign.equals(signFromXml)) {
return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名错误]]></return_msg></xml>";
}
// 处理异步通知
String outTradeNo = params.get("out_trade_no");
String totalFee = params.get("total_fee");
// 处理订单逻辑
return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
}
private String getSign(Map<String, String> params, String key) {
StringBuilder sb = new StringBuilder();
for (String k : params.keySet()) {
if ("sign".equals(k)) {
continue;
}
String v = params.get(k);
if (v != null && v.length() > 0) {
sb.append(k).append("=").append(v).append("&");
}
}
sb.append("key=").append(key);
String sign = md5(sb.toString()).toUpperCase();
return sign;
}
private String md5(String str) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] b = md.digest(str.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder(32);
for (byte by : b) {
sb.append(String.format("%02X", by & 0xFF));
}
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
- 运行Spring Boot项目,在浏览器中访问http://localhost:8080,输入订单金额后,点击"生成二维码"按钮即可生成微信付款二维码。
以上就是一个简单的二维码微信付款的Spring Boot Maven项目实现。注意,本示例中仅仅处理了生成预订单并生成二维码的流程,真实应用中还需要处理用户实际支付成功后的异步通知并完成订单逻辑。