微信二维码支付-模式一(PC端,解决中文乱码问题)

时间:2022-09-06 05:52:32

近期公司调完银联,调支付宝,调完支付宝调微信.说实话微信的帮助文档确实是烂,而且有没有技术支持,害的我头发都掉了一桌.不说废话了,看代码.

首先登陆微信的公众平台(微信的服务号不是订阅号),然后选择微信支付-->开发设置,设置好支付回调URL和支付授权目录(授权目录最少精确到二级目录,比如你的需要使用微信支付的目录是:www.weixinpay.com/sp/weixin/pay.do,那么对应的是:www.weixinpay.com/sp/weixin/),设置好后编写代码.

对了,联调支付,一般都需要外网能够访问的URL地址,这里建议使用过ngrok软件,直接在本地联调,使用方式,下载一个ngrok,然后由命令行窗口进入ngrok解压的目录,然后执行:

ngrok.exe -config ngrok.cfg -subdomain weixinpay(可以改成自己喜欢的域名) 8080(可以改成自己喜欢的端口)

首先生成微信二维码(1):

 /**
* 生成微信二维码图片
* @throws Exception
*/
public void gainQRCode() throws Exception {
try {
String orderNu = request.getParameter("orderNu");
String describe = request.getParameter("describe");
String payCodeType = request.getParameter("payCodeType");
System.out.println("订单编号:"+"\n"+orderNu);
String price = request.getParameter("txnAmt");
if(StringUtils.isBlank(orderNu)){//账户充值,不用签名校验
orderNu = "WE"+StringUtil.getTableId(false);
} else {
String sign = request.getParameter("sign");
String signParam = "orderNu="+orderNu+"&payPrice="+price;
String newSign = DigestUtils.md5Hex(signParam.getBytes("utf-8"));
if(!newSign.equalsIgnoreCase(sign)){
Map param = new HashMap<>();
param.put("statu", "2");
JSONArray jsonProduct = JSONArray.fromObject(param);
System.out.println("json: "+jsonProduct.toString());
response.getWriter().print(jsonProduct.toString());
return ;
}
}
System.out.println("没有转换的金额:"+"\n"+price);
BigDecimal bigDecimalPrice = new BigDecimal(price);
String pric = bigDecimalPrice.multiply(new BigDecimal(100)).toString().split("\\.")[0];
System.out.println("转换后的金额:"+"\n"+pric);
String filePostfix = "jpg";
// 二维码图片名称
String codePng = System.currentTimeMillis() + "." + filePostfix;
// 保存路径
// 应用ID
String appid = Config.APPID;
// 商户ID
String mch_id = Config.MCHID;
String key = Config.KEY;
// 生成32位的随机字符串
String nonce_str = RandomStringUtil.generate().toUpperCase();
// 商户产品ID
User user = SessionUtil.getSysUserFormSession(request);
String product_id = "";
if(StringUtils.isNotBlank(payCodeType) && "1".equals(payCodeType)){//账户充值
product_id = "1_"+orderNu+"_"+"账户保证金充值"+"_"+pric+"_"+user.getUniversalid();
} else if (StringUtils.isNotBlank(payCodeType) && "2".equals(payCodeType)){//订单结算
product_id = "2_"+orderNu+"_"+describe+"_"+pric+"_"+user.getUniversalid();
}
         //上面的可以不关注.关注下面的签名和发送的参数,54行开始
// 时间戳
String time_stamp = System.currentTimeMillis()+"";
String sign = "";//这些参数可以去微信扫码的支付的文档看看具体是什么意思
String temp = "appid=" + appid + "&mch_id=" + mch_id + "&nonce_str=" + nonce_str + "&" + "product_id="
+ product_id + "&time_stamp=" + time_stamp;
String signTemp = temp + "&key=" + key;
System.out.println("二维码请求参数:"+"\n"+temp);
sign = Encrypt.e(signTemp).toUpperCase();
String contentUrl = "weixin://wxpay/bizpayurl?" + temp + "&sign=" + sign;
String url = QRCODE_PATH + File.separator + File.separator + codePng;
File f = new File(url);
if(!f.exists()){
f.mkdirs();
}
System.out.println("已生成二维码url");
QRImgCode.encode(contentUrl, "二维码", 400, 400, url);
System.out.println("二维码已经生成成功");
Map param = new HashMap<>();
param.put("statu", "1");
param.put("imgName", codePng);
System.out.println("返回参数封装成功");
JSONArray jsonProduct = JSONArray.fromObject(param);
System.out.println("json: "+jsonProduct.toString());
response.getWriter().print(jsonProduct.toString());
} catch (Exception e) {
throw e;
}
}

生成二维码(2):

 /**
* 无中间图片
* @param content
* @param width
* @param height
* @param destImagePath
*/
public static void encode(String content,String name,int width, int height, String destImagePath) {
try {
System.err.println("进入二维码方法");
BitMatrix bitMatrix = genBarcode(content, 190, 184);
System.out.println("生成二维码数据");
MatrixToImageWriter.writeToFile(bitMatrix, "jpg", new File(destImagePath));
System.out.println("二维码图片生成完成");
} catch (IOException e) {
e.printStackTrace();
} catch (WriterException e) {
e.printStackTrace();
}
}

PC端扫码后微信会去调用调用支付回调URL,接下来重要的步骤来了:(险些让我秃顶)

 /**
* 微信扫码之后的回调
* @return
*/
public void weixinPay() {
System.out.println("微信支付扫码回调,手动发起统一下单支付");
try {
InputStream inStream = this.request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
String re = new String(outSteam.toByteArray(), "utf-8");
System.out.println("用户扫码后返回的参数: \n" + re);
      //转xml 微信传输的是xml
Map map = parseXML(re);
      //扫码后签名验证
String sign = gainValidateSign(map,false).toUpperCase();
if (!sign.equalsIgnoreCase((String)map.get("sign"))) {
System.out.println("扫码后生成的签名不正确");
return;
}//这些是扫码后的验证
System.out.println("扫码后生成的签名正确, 继续进行后续操作"); String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; String string = (String)map.get("product_id");
System.out.println("扫码的product_id参数:\n" + string);
String[] para = string.split("_");
//这里个方法是生成与支付订单的请求参数,看下面的方法,需要传输的参数可以去查看微信的帮助文档,虽然烂,但是这些还是有的.
StringEntity param = genProductArgs(para[0] + "_" + para[1], para[2], para[3]);
String result = sendPost(url, param);
System.out.println("发送请求得到结果===" + result); Map resultMap = parseXML(result);
String signResult = gainSign(resultMap);
if (!signResult.equalsIgnoreCase((String)resultMap.get("sign"))) {
System.out.println("统一下单接口-->签名不正确");
return;
}
if (StringUtils.isNotBlank(para[0]) && "1".equals(para[0])) {
RechargePrice r = rechargePriceServiceService.findByorderIdRecharge(para[1]);
if(r == null || r.getUniversalid() == null){
RechargePrice rp = new RechargePrice();
rp.setUserId(para[4]);
rp.setCreateDate(DateFormatUtil.strToDate(UtilDate.getDateFormatter()));
rp.setExplain("账户充值");
rp.setFlowAccountNum(StringUtil.getTableId(true));
BigDecimal bigDecimalPrice = new BigDecimal(para[3]); double doubleValue = bigDecimalPrice.divide(new BigDecimal("100")).doubleValue();
rp.setPrice(doubleValue+"");
rp.setOrderCode(para[1]);
rp.setTransactionStat("2");
rp.setTransactionType("1");
rp.setPaySource("5");
this.rechargePriceServiceService.saveRechargePrice(rp);
}
}
ServletOutputStream outputStream = this.response.getOutputStream(); String return_code = (String)resultMap.get("return_code");
String result_code = (String)resultMap.get("result_code");
if (StringUtils.isNotBlank(return_code) && StringUtils.isNotBlank(result_code) && return_code.equalsIgnoreCase("SUCCESS") && result_code.equalsIgnoreCase("SUCCESS")) {
String xml = genPay(resultMap);//对于中文乱码主要是上面的那个预支付订单请求,只要上面请求OK,这个请求就可以不用转码
System.out.println("统一下单接口后,向微信发送支付其你去的xml"+"\n" + xml);
outputStream.write(xml.getBytes());
outputStream.flush();
outputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}

字符串转xml:

 /**
* 解析xml方法
*/
@SuppressWarnings("rawtypes")
public Map<String, String> parseXML(String xml) {
Document doc;
Map<String, String> map = new LinkedHashMap<String, String>();
try {
doc = DocumentHelper.parseText(xml);
Element rootElement = doc.getRootElement();
Iterator elementIterator = rootElement.elementIterator();
while (elementIterator.hasNext()) {
Element recordEle = (Element) elementIterator.next();
String name = recordEle.getName();
String textTrim = recordEle.getTextTrim();
map.put(name, textTrim);
}
} catch (DocumentException e) {
e.printStackTrace();
}
return map;
}
处理xml集合,返回签名:
 /**
* 处理xml字符串 返回签名
*/
public String gainValidateSign(Map<String, String> map, boolean isUtf8) {
StringBuffer sb = new StringBuffer();
Set<String> keySet = map.keySet();
List<String> list = new LinkedList<String>();
list.addAll(keySet);
Collections.sort(list);
for (String key : list) {
if (!key.equals("sign")) {
sb.append(key).append("=").append(map.get(key)).append("&");
}
}
sb.append("key=").append(Config.KEY);
String sign = "";
if(isUtf8){
try {
sign = Encrypt.e(new String(sb.toString().getBytes(), "utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else {
sign = Encrypt.e(sb.toString());
}
return sign;
}

封装请求参数返回xml字符串:

 /**
* 把一个参数添加到 一个集合中,按字典顺序,这是为了后面生成 签名方便
* @param attach
* @param map
*
* @return
* @throws Exception
*/
private StringEntity genProductArgs(String attach,String body,String price) throws Exception {
List<NameValuePair> packageParams = new LinkedList<NameValuePair>();
packageParams.add(new BasicNameValuePair("appid", Config.APPID));
packageParams.add(new BasicNameValuePair("attach", attach));
packageParams.add(new BasicNameValuePair("body", body));
packageParams.add(new BasicNameValuePair("mch_id", Config.MCHID));
packageParams.add(new BasicNameValuePair("nonce_str", RandomStringUtil.generate().toUpperCase()));
packageParams.add(new BasicNameValuePair("notify_url", ""));
packageParams.add(new BasicNameValuePair("out_trade_no", com.eryansky.common.utils.StringUtils.getRandomNumbersAndLetters(15).toUpperCase()));
packageParams.add(new BasicNameValuePair("spbill_create_ip", request.getRemoteAddr()));
packageParams.add(new BasicNameValuePair("total_fee", Integer.parseInt(price)+""));
packageParams.add(new BasicNameValuePair("trade_type", Config.trade_type));
// 调用genXml()方法获得xml格式的请求数据
// String genXml = null;
try {
StringEntity stringEntityXml = getStringEntityXml(packageParams);
return stringEntityXml;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

返回发送的xml数据:(解决中文乱码问题)

 /**
* 生成xml文档发送微信生成支付信息
*
* @param params
* @return
* @throws Exception
*/
private StringEntity getStringEntityXml(List<NameValuePair> params) throws Exception {
StringBuilder sb = new StringBuilder();
StringBuilder sb2 = new StringBuilder();
sb2.append("<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><xml>");
for (int i = 0; i < params.size(); i++) {
// sb是用来计算签名的
sb.append(params.get(i).getName());
sb.append('=');
sb.append(params.get(i).getValue());
sb.append('&');
// sb2是用来做请求的xml参数
sb2.append("<" + params.get(i).getName() + ">");
sb2.append(params.get(i).getValue());
sb2.append("</" + params.get(i).getName() + ">");
}
sb.append("key=");
sb.append(Config.KEY);
System.err.println("生成签名的参数:"+"\n"+sb.toString());
String packageSign = null;
// 生成签名-->签名和xml字符串都需要转成utf-8格式,中文就不会出现乱码
packageSign = DigestUtils.md5Hex(sb.toString().getBytes("utf-8")).toUpperCase();
// packageSign = DigestUtils.md5Hex(sb.toString()).toUpperCase();
System.out.println("生成发送统一接口的签名:"+"\n"+packageSign);
sb2.append("<sign><![CDATA[");
sb2.append(packageSign);
sb2.append("]]></sign>");
sb2.append("</xml>");
System.err.println("生成发送统一接口的xml"+"\n"+sb2.toString());
try {
StringEntity se = new StringEntity(sb2.toString(), "utf-8");
return se;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

发送请求:

 /**
* 发送请求
* @throws UnsupportedEncodingException
*/
public String sendPost(String url, StringEntity param) throws UnsupportedEncodingException {
String reslt = "";
try {
CloseableHttpClient httpClient = HttpClients.createDefault();
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(30000).build();
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader("Content-Type", "text/xml");
httpPost.setEntity(param);
httpPost.setConfig(requestConfig);
HttpResponse response = httpClient.execute(httpPost);
HttpEntity entity = response.getEntity();
reslt = EntityUtils.toString(entity, "UTF-8");
} catch (ParseException | IOException e1) {
e1.printStackTrace();
} //下面注释掉的直接忽略掉,贴上来主要想说,以前用这种方法中文乱码,而且用utf-8转换一下后直接报签名错误,把签名信息贴到微信的签名验证上面确实可以通过,真的是无语
// System.out.println("进入发送统一下单支付方法");
// PrintWriter out = null;
// BufferedReader in = null;
//
// String result = "";
// try {
// URL realUrl = new URL(url);
//
// URLConnection conn = realUrl.openConnection();
//
// conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
//
// conn.setDoOutput(true);
// conn.setDoInput(true);
//
// out = new PrintWriter(conn.getOutputStream());
// System.out.println("请求参数========" + param);
//// param = new String(param.getBytes(), "utf-8");
//
// out.write(param);
//
// out.flush();
//
// in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
// String line;
// while ((line = in.readLine()) != null)
// {
// result = result + line;
// }
// } catch (Exception e) {
// e.printStackTrace();
// try
// {
// if (out != null) {
// out.close();
// }
// if (in != null)
// in.close();
// }
// catch (IOException ex) {
// ex.printStackTrace();
// }
// }
// finally
// {
// try
// {
// if (out != null) {
// out.close();
// }
// if (in != null)
// in.close();
// }
// catch (IOException ex) {
// ex.printStackTrace();
// }
// }
// return new String(result.getBytes(), "utf-8");
return reslt;
}

拼接微信支付信息参数:

 /**
* 发送支付信息参数
* @param param
* @return
*/
public String genPay(Map<String, String> param) {
System.out.println("向微信发送支付请求的 参数-->"+"\n"+param.toString());
List<NameValuePair> packageParams = new LinkedList<NameValuePair>();
packageParams.add(new BasicNameValuePair("appid", param.get("appid")));
packageParams.add(new BasicNameValuePair("mch_id", param.get("mch_id")));
packageParams.add(new BasicNameValuePair("nonce_str", RandomStringUtil.generate().toUpperCase()));
packageParams.add(new BasicNameValuePair("prepay_id", param.get("prepay_id")));
packageParams.add(new BasicNameValuePair("result_code", param.get("result_code")));
packageParams.add(new BasicNameValuePair("return_code", param.get("return_code")));
if ("FAIL".equalsIgnoreCase(param.get("result_code"))) {
packageParams.add(new BasicNameValuePair("return_code", param.get("result_code")));
if(StringUtils.isBlank(param.get("err_code_des"))){
packageParams.add(new BasicNameValuePair("err_code_des", "订单时效"));
}
packageParams.add(new BasicNameValuePair("err_code_des", param.get("err_code_des")));
}
String genXml = null;
try {
System.out.println("向微信发送支付请求的xml"+"\n"+packageParams.toString());
genXml = genXml(packageParams);
} catch (Exception e) {
e.printStackTrace();
}
return genXml;
}

生成微信支付xml参数:

 /**
* 生成xml文档发送个给微信支付
*
* @param params
* @return
* @throws Exception
*/
private String genXml(List<NameValuePair> params) throws Exception {
StringBuilder sb = new StringBuilder();
StringBuilder sb2 = new StringBuilder();
sb2.append("<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><xml>");
for (int i = 0; i < params.size(); i++) {
// sb是用来计算签名的
sb.append(params.get(i).getName());
sb.append('=');
sb.append(params.get(i).getValue());
sb.append('&');
// sb2是用来做请求的xml参数
sb2.append("<" + params.get(i).getName() + ">");
sb2.append(params.get(i).getValue());
sb2.append("</" + params.get(i).getName() + ">");
}
sb.append("key=");
sb.append(Config.KEY);
System.err.println("生成签名的参数:"+"\n"+sb.toString());
String packageSign = null;
// 生成签名
packageSign = DigestUtils.md5Hex(sb.toString()).toUpperCase();
System.out.println("生成发送统一接口的签名:"+"\n"+packageSign);
sb2.append("<sign><![CDATA[");
sb2.append(packageSign);
sb2.append("]]></sign>");
sb2.append("</xml>");
System.err.println("生成发送统一接口的xml"+"\n"+sb2.toString());
try {
return sb2.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}

支付成功后:

 /**
* 微信扫码支付成功后的回调的接口
* @throws IOException
*/
public void weiXinnotify() throws IOException{
InputStream inStream = this.request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
String re = new String(outSteam.toByteArray(), "utf-8");
System.out.println("用户扫码后支付返回的参数: \n" + re);
Map parseXML = parseXML(re);
System.out.println("转成xml后的map:\n" + parseXML);
String string = (String)parseXML.get("attach");
//处理完逻辑后记得向微信发送消息,不然微信会隔一段时间会访问
PrintWriter writer = response.getWriter();
writer.print(re);
writer.close();
}

最后,对于微信的帮助文档,是我目前见过最烂的了,前面联调支付宝和银联都没这样.哎不吐槽了,

本博客是根据:

http://www.cnblogs.com/zyw-205520/p/5495115.html这篇博客,写的很好,他自己写了一个开源项目,

以及和IT好的帮助下完成的,

如果有好的博客可以推荐给我,大家共同学习,谢谢!!