关于准备工作,就“微信扫码支付模式二”官方文档地址在这https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
对于“微信扫码支付模式二”(支付与回调)实际只会用到APP_ID、MCH_ID和API_KEY,其他的都不用。
自己用springBoot搭建一个简单的web项目,把下面代码放在项目里跑就行了,不需要额外的jar包。由于是自己测试用的,代码是不是很简洁。1.WechatPay类是自定义对象,用于传参数的,可以去掉。2.微信配置参数更换一下,把“x x x x x x”换掉就可以了。
返回二维码
**一,**不用sdk和jar包,直接是调微信的api。
前端调第一个接口,返回二维码,要是前端自己生成二维码,返回urlCode就可以了。用户扫码支付成功,微信会调第二个接口。前端循环调第三个接口,判断用户是否支付。
(1)主要的业务代码
package com.controller;
import com.domain.WechatPay;
import com.sun.org.apache.regexp.internal.RE;
import com.util.*;
import org.springframework.web.bind.annotation.*;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
/**
* @author zhubin
* @version 0.1
* @Pacakage com.controller
* @Description 微信支付
* @create 2019/12/04 11:17
**/
@RestController
@RequestMapping("/wechat")
public class WechatPayController {
@PostMapping("/getPayQRcode")
public void getWechatPayQRcode(@RequestBody WechatPay wechatPay, HttpServletRequest request, HttpServletResponse response){
//实际开发,这三个参数可以由前端传,或从数据库查询
// 价格 注意:价格的单位是分
String order_price = "1";
// 商品描述
String body = "为科技充值";
// 商户订单号
String out_trade_no = "113389054";
String currTime = PayCommonUtil.getCurrTime();
String strTime = currTime.substring(8, currTime.length());
String strRandom = PayCommonUtil.buildRandom(4) + "";
//随机字符串
String nonce_str = strTime + strRandom;
// 获取发起电脑 ip(终端ip)
String spbill_create_ip = PayCommonUtil.getRemoteAddr(request);
SortedMap<Object,Object> packageParams = new TreeMap<>();
// 公众号id
packageParams.put("appid", PayConfigUtil.APP_ID);
//商户号
packageParams.put("mch_id", PayConfigUtil.MCH_ID);
//随机字符串
packageParams.put("nonce_str", nonce_str);
//商品描述
packageParams.put("body", body);
//商品订单号
packageParams.put("out_trade_no", out_trade_no);
//订单金额
packageParams.put("total_fee", order_price);
//发起者ip
packageParams.put("spbill_create_ip", spbill_create_ip);
// 回调接口
packageParams.put("notify_url", PayConfigUtil.NOTIFY_URL);
// 交易类型
packageParams.put("trade_type", PayConfigUtil.TRADE_TYPE);
// 签名
String sign = PayCommonUtil.createSign("UTF-8", packageParams,PayConfigUtil.API_KEY);
packageParams.put("sign", sign);
String requestXML = PayCommonUtil.getRequestXml(packageParams);
String resXml = HttpUtil.postData(PayConfigUtil.UFDODER_URL, requestXML);
Map<String,String> map = XMLUtil.convertToMap(resXml);
String returnCode = map.get("return_code");
if ("FAIL".equals(returnCode)){
String return_msg = map.get("return_msg");
System.out.println("支付请求异常----------"+return_msg);
return;
}
String resultCode = map.get("result_code");
if ("SUCCESS".equals(returnCode) && "FAIL".equals(resultCode)){
String errCodeDes = map.get("err_code_des");
System.out.println("业务异常-----------"+errCodeDes);
return;
}
String urlCode = map.get("code_url");
BufferedImage bufferedImage = PayCommonUtil.writeInfoToJpgBuff(urlCode);
try {
ImageIO.write(bufferedImage, "png", response.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 微信异步回调
* @param request
* @param response
* @throws Exception
*/
@PostMapping("/otify")
public void weixin_notify(HttpServletRequest request, HttpServletResponse response) throws Exception{
//读取参数
InputStream inputStream ;
StringBuffer sb = new StringBuffer();
inputStream = request.getInputStream();
String s ;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((s = in.readLine()) != null){
sb.append(s);
}
in.close();
inputStream.close();
//解析xml成map
Map<String, String> m = new HashMap<String, String>();
m = XMLUtil.convertToMap(sb.toString());
System.out.println(m);
//过滤空 设置 TreeMap
SortedMap<Object,Object> packageParams = new TreeMap<>();
Iterator it = m.keySet().iterator();
while (it.hasNext()) {
String parameter = (String) it.next();
String parameterValue = m.get(parameter);
String v = "";
if(null != parameterValue) {
v = parameterValue.trim();
}
packageParams.put(parameter, v);
}
// 账号信息
String key = PayConfigUtil.API_KEY;
//logger.info(packageParams);
//判断签名是否正确
if(PayCommonUtil.isTenpaySign("UTF-8", packageParams,key)) {
//------------------------------
//处理业务开始
//------------------------------
String resXml = "";
if("SUCCESS".equals(packageParams.get("result_code"))){
// 这里是支付成功
//执行自己的业务逻辑
String mch_id = (String)packageParams.get("mch_id");
String openid = (String)packageParams.get("openid");
String is_subscribe = (String)packageParams.get("is_subscribe");
String out_trade_no = (String)packageParams.get("out_trade_no");
String total_fee = (String)packageParams.get("total_fee");
System.out.println("mch_id:"+mch_id);
System.out.println("openid:"+openid);
System.out.println("is_subscribe:"+is_subscribe);
System.out.println("out_trade_no:"+out_trade_no);
System.out.println("total_fee:"+total_fee);
//执行自己的业务逻辑
//logger.info("支付成功");
//通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
} else {
//logger.info("支付失败,错误信息:" + packageParams.get("err_code"));
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
//------------------------------
//处理业务完毕
//------------------------------
BufferedOutputStream out = new BufferedOutputStream(
response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
} else{
//logger.info("通知签名验证失败");
System.out.println("通知签名验证失败");
}
}
/**
* 订单是否注册
* @return
*/
@PostMapping("/ynPay")
public String ynPayByOrderId(@PathVariable("orderId") String orderId){
/*WechatPay wechatPay = WechatPayService.ynPayByOrderId(orderId);
if (null != wechatPay && wechatPay.getOrderStatus() == 2){
return "true";
}*/
return "false";
}
}
(2)工具类
PayConfigUtil
把这个类里的“xxxxxxxxx”更换掉,就可以跑了
package com.util;
/**
* @author zhubin
* @Pacakage com.util
* @Description
* @create 2019/12/04 12:13
**/
public class PayConfigUtil {
/**
* appid
*/
public static final String APP_ID = "xxxxxxxxxx";
/**
* 商业号
*/
public static final String MCH_ID = "xxxxxxxxxxx";
/**
* 交易类型
*/
public static final String TRADE_TYPE = "NATIVE";
/**
* key
*/
public static final String API_KEY = "xxxxxxxxxxxx";
/**
* 回调url
*/
public static final String NOTIFY_URL = "http://xxxxxxxxx/wechat/wechatNotify";
/**
* 微信请求地址
*/
public static final String UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
}
HttpUtil
package com.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
/**
* @author zhubin
* @Pacakage com.util
* @Description
* @create 2019/12/04 13:49
**/
public class HttpUtil {
// in milliseconds
private final static int CONNECT_TIMEOUT = 5000;
private final static String DEFAULT_ENCODING = "UTF-8";
public static String postData(String urlStr, String data){
return postData(urlStr, data, null);
}
public static String postData(String urlStr, String data, String contentType){
BufferedReader reader = null;
try {
URL url = new URL(urlStr);
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
conn.setConnectTimeout(CONNECT_TIMEOUT);
conn.setReadTimeout(CONNECT_TIMEOUT);
if(contentType != null){
conn.setRequestProperty("content-type", contentType);
}
OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), DEFAULT_ENCODING);
if(data == null){
data = "";
}
writer.write(data);
writer.flush();
writer.close();
reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), DEFAULT_ENCODING));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append("\r\n");
}
return sb.toString();
} catch (IOException e) {
} finally {
try {
if (reader != null){
reader.close();
}
} catch (IOException e) {
}
}
return null;
}
}
MD5Util
package com.util;
import java.security.MessageDigest;
/**
* @author zhubin
* @Pacakage com.util
* @Description
* @create 2019/12/04 13:42
**/
public class MD5Util {
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));
return resultSb.toString();
}
private static String byteToHexString(byte b) {
int n = b;
if (n < 0){
n += 256;
}
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
public static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname)) {
resultString = byteArrayToHexString(md.digest(resultString
.getBytes()));
}else {
resultString = byteArrayToHexString(md.digest(resultString
.getBytes(charsetname)));
}
} catch (Exception exception) {
}
return resultString;
}
private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
}
PayCommonUtil
package com.util;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
import javax.servlet.http.HttpServletRequest;
import java.awt.image.BufferedImage;
import java.net.InetAddress;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* @author zhubin
* @Pacakage com.util
* @Description
* @create 2019/12/04 13:41
**/
public class PayCommonUtil {
/**
* 是否签名正确,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
* @return boolean
*/
public static boolean isTenpaySign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
String v = (String)entry.getValue();
if(!"sign".equals(k) && null != v && !"".equals(v)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + API_KEY);
//算出摘要
String mysign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toLowerCase();
String tenpaySign = ((String)packageParams.get("sign")).toLowerCase();
//System.out.println(tenpaySign + " " + mysign);
return tenpaySign.equals(mysign);
}
/**
* @author
* @date 2016-4-22
* @Description:sign签名
* @param characterEncoding
* 编码格式
* @param
*
* @return
*/
public static String createSign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + API_KEY);
String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
/**
* @author
* @date 2016-4-22
* @Description:将请求参数转换为xml格式的string
* @param parameters
* 请求参数
* @return
*/
public static String getRequestXml(SortedMap<Object, Object> parameters) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = parameters.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {
sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
} else {
sb.append("<" + k + ">" + v + "</" + k + ">");
}
}
sb.append("</xml>");
return sb.toString();
}
/**
* 取出一个指定长度大小的随机正整数.
*
* @param length
* int 设定所取出随机数的长度。length小于11
* @return int 返回生成的随机数。
*/
public static int buildRandom(int length) {
int num = 1;
double random = Math.random();
if (random < 0.1) {
random = random + 0.1;
}
for (int i = 0; i < length; i++) {
num = num * 10;
}
return (int) ((random * num));
}
/**
* 获取当前时间 yyyyMMddHHmmss
*
* @return String
*/
public static String getCurrTime() {
Date now = new Date();
SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");
String s = outFormat.format(now);
return s;
}
private static final int BLACK = 0xFF000000;
private static final int WHITE = 0xFFFFFFFF;
/**
*二维码信息写成JPG BufferedImage
* @param content 二维码信�?
* @return JPG BufferedImage
*/
public static BufferedImage writeInfoToJpgBuff(String content){
BufferedImage re=null;
MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
Map hints = new HashMap();
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
try {
BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, 250, 250, hints);
re=toBufferedImage(bitMatrix);
} catch (Exception e) {
e.printStackTrace();
}
return re;
}
/**
* 根据二维矩阵的碎�? 生成对应的二维码图像缓冲
* @param matrix 二维矩阵的碎�? 包含 宽高 行,字节
* @see com.google.zxing.common.BitMatrix
* @return 二维码图像缓�?
*/
public static BufferedImage toBufferedImage(BitMatrix matrix) {
int width = matrix.getWidth();
int height = matrix.getHeight();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);
}
}
return image;
}
/**
* 获取发起者ip
* @param request
* @return
*/
public static String getRemoteAddr(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
if (ip.indexOf(",") != -1) {
ip = ip.split(",")[0];
}
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if (ip.equals("127.0.0.1") || ip.equals("0:0:0:0:0:0:0:1")) {
// 根据网卡获取本机配置的IP地址
InetAddress inetAddress = null;
try {
inetAddress = InetAddress.getLocalHost();
} catch (Exception e) {
e.printStackTrace();
}
ip = inetAddress.getHostAddress();
}
return ip;
}
}
XMLUtil
package com.util;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.StringReader;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author zhubin
* @Pacakage com.util
* @Description
* @create 2019/12/04 13:51
**/
public class XMLUtil {
/**
* xml转map
* @param xml
* @return
*/
public static Map<String,String> convertToMap(String xml){
Map<String, String> map = new LinkedHashMap<>();
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
StringReader sr = new StringReader(xml);
InputSource is = new InputSource(sr);
Document document = db.parse(is);
Element root = document.getDocumentElement();
if(root != null){
NodeList childNodes = root.getChildNodes();
if(childNodes != null && childNodes.getLength()>0){
for(int i = 0;i < childNodes.getLength();i++){
Node node = childNodes.item(i);
if( node != null && node.getNodeType() == Node.ELEMENT_NODE){
map.put(node.getNodeName(), node.getTextContent());
}
}
}
}
} catch (DOMException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return map;
}
}