整体的实现思路:自定义一个接口,用于生成二维码展示到页面,此时需要用到微信提供的sdk,以及自己的一些业务数据,比如:金额等;再定义一个异步通知接口,用于微信回调访问,同时可以处理一些业务数据及流水记录等。
1.下载sdk
2.引入sdk相关代码
3.整体代码实现如下:
控制层:
DemoController:一个Demo的入口;两个重要的方法,一个是生成二维码的请求,另一个是异步获取业务的状态(该订单支付完成的状态),当然也可以采用socket的方式,此例子为了简单化只用了setInterval,该代码在index.html
package com.example.demo.controller;import java.util.Map;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.ResponseBody;import com.example.demo.pay.service.impl.WxCreatePaySerivceImpl;import com.example.demo.service.ProductorService;import com.example.demo.service.Productorder;import com.example.demo.service.ResultMap;@Controllerpublic class DemoController {@Autowiredprivate WxCreatePaySerivceImpl createPaySerivce;@Autowiredprivate ProductorService productorService;@GetMapping("/")public String hello() {System.out.println("hello world!");return "index";}@GetMapping("/cpsPay")public void cpsPay(HttpServletRequest request,HttpServletResponse response) {System.out.println("cpsPay!");try {String ac = request.getParameter("ac");if("wxpay".equals(ac)) {createPaySerivce.doCreate(request, response);}} catch (Exception e) {e.printStackTrace();}}@PostMapping("/payStatus")@ResponseBodypublic Map payStatus(HttpServletRequest request,HttpServletResponse response) {System.out.println("payStatus!");try {Productorder productorder = productorService.getById(request.getParameter("orderno"));if("1".equals(productorder.getOrderstatus())) {return ResultMap.successRes();}} catch (Exception e) {e.printStackTrace();}return ResultMap.errorRes("error");}}
WxCallbackController: 用户微信异步通知的接口,该url会配置到下面的ConfigUtil类中
package com.example.demo.controller;import java.io.BufferedReader;import java.io.InputStream;import java.io.InputStreamReader;import java.math.BigDecimal;import java.text.SimpleDateFormat;import java.util.HashMap;import java.util.Map;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import com.example.demo.pay.util.ConfigUtil;import com.example.demo.service.ProductorService;import com.example.demo.service.Productorder;import com.example.demo.service.wxpay.sdk.WXPayUtil;public class WxCallbackController {@Autowiredprivate ProductorService productorService;@RequestMapping(value="/notifyWxPay",produces="text/html;charset=utf-8")@ResponseBody public String notifyWxPay(HttpServletRequest request, HttpServletResponse response) throws Exception {Map return_data = new HashMap();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 map = WXPayUtil.xmlToMap(sb.toString()); //判断签名是否正确 // if(true) { if(WXPayUtil.isSignatureValid(map, ConfigUtil.API_KEY)) { if(!map.get("return_code").toString().equals("SUCCESS")){ return_data.put("return_code", "FAIL"); return_data.put("return_msg", "return_code不正确"); }else{ if(!map.get("result_code").toString().equals("SUCCESS")){ return_data.put("return_code", "FAIL"); return_data.put("return_msg", "result_code不正确"); return WXPayUtil.mapToXml(return_data); } String orderno = (String)map.get("out_trade_no");//商户订单号 String transaction_id = (String)map.get("transaction_id");//微信支付订单号 String time_end = (String)map.get("time_end");//支付完成时间yyyyMMddHHmmss BigDecimal total_fee = new BigDecimal(map.get("total_fee").toString()); //付款完成后,支付宝系统发送该交易状态通知 Productorder order = productorService.getById(orderno); if(order==null) { System.out.println("订单不存在"); return_data.put("return_code", "FAIL"); return_data.put("return_msg", "订单不存在"); return WXPayUtil.mapToXml(return_data); } BigDecimal num = new BigDecimal("100"); BigDecimal ordermoney = new BigDecimal(order.getOrdermoney()); ordermoney = ordermoney.multiply(num); //订单已经支付 if(order.getOrderstatus().equals("1")){ System.out.println("订单已经支付"); return_data.put("return_code", "SUCCESS"); return_data.put("return_msg", "OK"); return WXPayUtil.mapToXml(return_data); } //如果支付金额不等于订单金额返回错误 if(ordermoney.compareTo(total_fee)!=0){ System.out.println("资金异常"); return_data.put("return_code", "FAIL"); return_data.put("return_msg", "金额异常"); return WXPayUtil.mapToXml(return_data); } //更新订单信息 try { System.out.println("更新订单信息"); SimpleDateFormat sdf1=new SimpleDateFormat("yyyyMMddHHmmss"); SimpleDateFormat sdf2=new SimpleDateFormat("YYYY-MM-dd hh:mm:ss"); order.setOrderstatus("1"); order.setPaytime(sdf2.format(sdf1.parse(time_end))); order.setPaymentway("1"); productorService.update(order); System.out.println("插入已经支付的订单表"); //插入支付流水TODO return_data.put("return_code", "SUCCESS"); return_data.put("return_msg", "OK"); return WXPayUtil.mapToXml(return_data);} catch (Exception e) {e.printStackTrace();return_data.put("return_code", "FAIL"); return_data.put("return_msg", "更新订单失败"); return WXPayUtil.mapToXml(return_data);} } } else{ System.out.println("通知签名验证失败"); return_data.put("return_code", "FAIL"); return_data.put("return_msg", "签名错误"); } return WXPayUtil.mapToXml(return_data);}}
工具类及服务:
CommonUtil:一个通用的工具类
package com.example.demo.pay.util;import java.io.BufferedReader;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.net.Inet4Address;import java.net.InetAddress;import java.net.URL;import java.net.UnknownHostException;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;import javax.net.ssl.HttpsURLConnection;import javax.servlet.http.HttpServletRequest;import org.springframework.util.StringUtils; public class CommonUtil {public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {try {URL url = new URL(requestUrl);HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();conn.setDoOutput(true);conn.setDoInput(true);conn.setUseCaches(false);// 设置请求方式(GET/POST)conn.setRequestMethod(requestMethod);conn.setRequestProperty("content-type", "application/x-www-form-urlencoded"); // 当outputStr不为null时向输出流写数据if (null != outputStr) {OutputStream outputStream = conn.getOutputStream();// 注意编码格式outputStream.write(outputStr.getBytes("UTF-8"));outputStream.close();}// 从输入流读取返回内容InputStream inputStream = conn.getInputStream();InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");BufferedReader bufferedReader = new BufferedReader(inputStreamReader);String str = null;StringBuffer buffer = new StringBuffer();while ((str = bufferedReader.readLine()) != null) {buffer.append(str);}// 释放资源bufferedReader.close();inputStreamReader.close();inputStream.close();inputStream = null;conn.disconnect();return buffer.toString();} catch (Exception e) {e.printStackTrace();}return null;}/** * 获取ip * @param request * @return */public static String getIp(HttpServletRequest request) {if (request == null)return "";String ip = request.getHeader("X-Requested-For");if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("X-Forwarded-For");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_CLIENT_IP");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_X_FORWARDED_FOR");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return ip;}public static String getOrderExpireTime(String timeStart, long l) {SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");try {Date date = sdf.parse(timeStart);Long lastTime = date.getTime() + l;return sdf.format(new Date(lastTime));} catch (ParseException e) {e.printStackTrace();}return null;}public static String getLocalIp(){InetAddress localHost = null;try { localHost = Inet4Address.getLocalHost();} catch (UnknownHostException e) {e.printStackTrace();}String ip = localHost.getHostAddress();return ip;}}
ConfigUtil:用于定义一些对接时必须的参数,涉及到多商户或者多微信时,以下可以做成动态的参数
package com.example.demo.pay.util;public class ConfigUtil {/** * 服务号相关信息 */public final static String APPID = "";// 服务号的应用号public final static String MCH_ID = "";// 商户号public final static String APP_SECRECT = "";// 服务号的应用密码public final static String API_KEY = "";// API密钥 自己定义的public final static String SIGN_TYPE = "MD5";// 签名加密方式public final static String CERT_PATH = "D:/project/cert/apiclient_cert.p12";// 微信支付证书存放路径地址public final static String TOKEN = "";// 服务号的配置token// 填写自己网站的接口public final static String NOTIFY_URL = "notifyWxPay";// 微信支付统一接口的回调actionpublic static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";}
服务层:
BaseService:在这里能够获取到需要的基础信息
package com.example.demo.pay.service;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.core.env.Environment;import org.springframework.stereotype.Service;import com.example.demo.pay.util.CommonUtil;@Servicepublic class BaseService {Logger logger = LoggerFactory.getLogger(this.getClass());@AutowiredEnvironment environment;public String getLocalPort(){ return environment.getProperty("local.server.port");}public String getContextPath(){ return environment.getProperty("server.servlet.context-path");}public String getLocalIp(){return CommonUtil.getLocalIp();}public String getLocalOutNetUrl(){return environment.getProperty("local.outnet.url");}public String getLocalContextPath(){String contextPath = this.getLocalOutNetUrl() + this.getContextPath() + "/";System.out.println("local context path:" + contextPath);return contextPath;}}
ICreatePayService:支付的对接接口,为以后会涉及到多方对接,故采用接口方式
package com.example.demo.pay.service;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public interface ICreatePayService {public void doCreate(HttpServletRequest request, HttpServletResponse response) throws Exception ;}
WxCreatePaySerivceImpl:上面接口的微信对接实现类
package com.example.demo.pay.service.impl;import java.text.DecimalFormat;import java.text.SimpleDateFormat;import java.util.Calendar;import java.util.Date;import java.util.Hashtable;import java.util.Map;import java.util.SortedMap;import java.util.TreeMap;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import com.example.demo.pay.service.BaseService;import com.example.demo.pay.service.ICreatePayService;import com.example.demo.pay.util.CommonUtil;import com.example.demo.pay.util.ConfigUtil;import com.example.demo.service.Productorder;import com.example.demo.service.wxpay.sdk.WXPayConstants.SignType;import com.example.demo.service.wxpay.sdk.WXPayUtil;import com.google.zxing.BarcodeFormat;import com.google.zxing.EncodeHintType;import com.google.zxing.MultiFormatWriter;import com.google.zxing.client.j2se.MatrixToImageWriter;import com.google.zxing.common.BitMatrix;@Service("wxpayCreatePaySerivce")public class WxCreatePaySerivceImpl implements ICreatePayService{@Autowiredprivate BaseService baseService;@Overridepublic void doCreate(HttpServletRequest request, HttpServletResponse response) throws Exception {String orderno = request.getParameter("orderno") == null ? "" : request.getParameter("orderno");Productorder p = new Productorder();Date date = new Date();SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");String timeStart = sdf.format(date);Calendar cal = Calendar.getInstance();cal.add(Calendar.DAY_OF_MONTH, 1);Date date1 = cal.getTime();String timeExpire = sdf.format(date1);SortedMap parameters = new TreeMap();parameters.put("appid", ConfigUtil.APPID);parameters.put("body", p.getPname());parameters.put("mch_id", ConfigUtil.MCH_ID);parameters.put("out_trade_no", orderno);parameters.put("spbill_create_ip", CommonUtil.getIp(request));DecimalFormat df = new DecimalFormat("#");parameters.put("total_fee", df.format(Double.parseDouble(p.getOrdermoney()) * 100));parameters.put("trade_type", "NATIVE");parameters.put("time_expire", CommonUtil.getOrderExpireTime(timeStart,5 * 60 * 1000L));// 二维码过期时间5分钟parameters.put("nonce_str", WXPayUtil.generateNonceStr());parameters.put("notify_url", baseService.getLocalContextPath() + ConfigUtil.NOTIFY_URL);// 支付成功后回调的action,与JSAPI相同String generateSignature = WXPayUtil.generateSignature(parameters, ConfigUtil.API_KEY, SignType.MD5);parameters.put("sign", generateSignature);String generateSignedXml = WXPayUtil.generateSignedXml(parameters, ConfigUtil.API_KEY);System.out.println("微信支付预下单请求xml格式::" + generateSignedXml);String result = CommonUtil.httpsRequest(ConfigUtil.UNIFIED_ORDER_URL, "POST", generateSignedXml);System.out.println(result);Map map;try {map = WXPayUtil.xmlToMap(result);String returnCode = map.get("return_code");String resultCode = map.get("result_code");//returnCode = "SUCCESS";//resultCode = "SUCCESS";if (returnCode.equalsIgnoreCase("SUCCESS") && resultCode.equalsIgnoreCase("SUCCESS")) {String codeUrl = map.get("code_url");//codeUrl = "testurl";// TODO 拿到codeUrl,写代码生成二维码System.out.println("codeUrl=" + codeUrl);int width = 300;int height = 300;// 二维码的图片格式String format = "JPEG";Hashtable hints = new Hashtable();// 内容所使用编码hints.put(EncodeHintType.CHARACTER_SET, "utf-8");BitMatrix bitMatrix = new MultiFormatWriter().encode(codeUrl, BarcodeFormat.QR_CODE, width, height,hints);// response.setContentType("image/JPEG");MatrixToImageWriter.writeToStream(bitMatrix, format, response.getOutputStream());}} catch (Exception e) {e.printStackTrace();}}}
index.html 生成二维码页面
Insert title here首页
pom.xml
<?xml version="1.0" encoding="UTF-8"?>4.0.0org.springframework.bootspring-boot-starter-parent2.1.13.RELEASEcom.exampledemo0.0.1-SNAPSHOTdemoDemo project for Spring Boot1.8org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-devtoolsruntimetrueorg.apache.httpcomponents httpclient 4.5.3org.slf4j slf4j-api 1.7.5org.slf4j slf4j-simple 1.7.21org.springframework.boot spring-boot-starter-thymeleafcom.google.zxing core 3.3.0com.google.zxing javase 3.2.1The BSD 3-Clause Licensehttps://opensource.org/licenses/BSD-3-Clauserepowxpayhttps://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=11_1releaseosshttps://oss.sonatype.org/content/repositories/snapshots/osshttps://oss.sonatype.org/service/local/staging/deploy/maven2/org.apache.maven.plugins maven-source-plugin 3.0.1packagejar-no-forkorg.apache.maven.plugins maven-javadoc-plugin 2.10.4packagejarorg.apache.maven.plugins maven-gpg-plugin 1.6sign-artifactsverifysignorg.springframework.bootspring-boot-maven-plugin
该例子只适用于对接微信二维码支付,给没有对接过支付接口的童鞋一些参考,老鸟可以忽略,大宝六