微信官方文档:https://pay.weixin.qq.com/wiki/doc/api/index.html

获取API证书:https://kf.qq.com/faq/161222NneAJf161222U7fARv.html

本地开发环境支付回调调试方法可以参考:

沙箱环境调试:

引入jar包。用于生成二维码

zxing-2.3.0.jar、IKAnalyzer2012_u6.jar

下载地址:https://yvioo.lanzous.com/b00nlbp6h                   密码:5jge 

MD5Util.java
import java.security.MessageDigest;
 
 
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" };
 
     
 
     
}

 

 

 

RandomUtil.java
import java.util.Random;
 
public class RandomUtil {
    private static char ch[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
          'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
          'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
          'x', 'y', 'z', '0', '1' };//最后又重复两个0和1,因为需要凑足数组长度为64
 
      private static Random random = new Random();
 
      //生成指定长度的随机字符串
      public static synchronized String createRandomString(int length) {
        if (length > 0) {
          int index = 0;
          char[] temp = new char[length];
          int num = random.nextInt();
          for (int i = 0; i < length % 5; i++) {
            temp[index++] = ch[num & 63];//取后面六位,记得对应的二进制是以补码形式存在的。
            num >>= 6;//63的二进制为:111111
            // 为什么要右移6位?因为数组里面一共有64个有效字符。为什么要除5取余?因为一个int型要用4个字节表示,也就是32位。
          }
          for (int i = 0; i < length / 5; i++) {
            num = random.nextInt();
            for (int j = 0; j < 5; j++) {
              temp[index++] = ch[num & 63];
              num >>= 6;
            }
          }
          return new String(temp, 0, length);
        }
        else if (length == 0) {
          return "";
        }
        else {
          throw new IllegalArgumentException();
        }
      }
 
 
       
}

 

 

 

SignUtil.java
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
 
 
public class SignUtil {
     /**
     * 微信支付签名算法sign
     * @param parameters
     * @return
     */ 
    @SuppressWarnings("unchecked") 
    public static String createSign(SortedMap<Object,Object> parameters,String key){ 
        StringBuffer sb = new StringBuffer(); 
        Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序(升序) 
        Iterator it = es.iterator(); 
        while(it.hasNext()) { 
            Map.Entry entry = (Map.Entry)it.next(); 
            String k = (String)entry.getKey(); 
            Object v = entry.getValue(); 
            if(null != v && !"".equals(v)  
                    && !"sign".equals(k) && !"key".equals(k)) { 
                sb.append(k + "=" + v + "&"); 
            } 
        } 
        sb.append("key=" + key); 
        String sign = MD5Util.MD5Encode(sb.toString(), "UTF-8").toUpperCase(); 
        return sign; 
    }
    /**
     * 与接口配置信息中的 token 要一致,这里赋予什么值,在接口配置信息中的Token就要填写什么值,
     * 两边保持一致即可,建议用项目名称、公司名称缩写等,我在这里用的是项目名称weixinface
     */
    private static String token = "HR9QhjKMCoUQlwd";
      
    /**
     * 验证签名
     * @param signature
     * @param timestamp
     * @param nonce
     * @return
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce){
        String[] arr = new String[]{token, timestamp, nonce};
        // 将 token, timestamp, nonce 三个参数进行字典排序
        Arrays.sort(arr);
        StringBuilder content = new StringBuilder();
        for(int i = 0; i < arr.length; i++){
            content.append(arr[i]);
        }
        MessageDigest md = null;
        String tmpStr = null;
          
        try {
            md = MessageDigest.getInstance("SHA-1");
            // 将三个参数字符串拼接成一个字符串进行 shal 加密
            byte[] digest = md.digest(content.toString().getBytes());
            tmpStr = byteToStr(digest);
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        content = null;
        // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
        return tmpStr != null ? tmpStr.equals(signature.toUpperCase()): false;
    }
      
    /**
     * 将字节数组转换为十六进制字符串
     * @param digest
     * @return
     */
    private static String byteToStr(byte[] digest) {
        // TODO Auto-generated method stub
        String strDigest = "";
        for(int i = 0; i < digest.length; i++){
            strDigest += byteToHexStr(digest[i]);
        }
        return strDigest;
    }
      
    /**
     * 将字节转换为十六进制字符串
     * @param b
     * @return
     */
    private static String byteToHexStr(byte b) {
        // TODO Auto-generated method stub
        char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        char[] tempArr = new char[2];
        tempArr[0] = Digit[(b >>> 4) & 0X0F];
        tempArr[1] = Digit[b & 0X0F];
          
        String s = new String(tempArr);
        return s;
    }
}

 

 

XmlPostUtil.java
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
 
public class XmlPostUtil {
    public static byte[] sendXmlRequest(String path, String params) throws Exception {
        URL url = new URL(path);
        System.out.println("发送xml:" + params);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");// 提交模式
        conn.setDoOutput(true);// 是否输入参数
        conn.setRequestProperty("Cache-Control", "no-cache");
        conn.setRequestProperty("Content-Type", "text/xml");
        byte[] bypes = params.toString().getBytes("UTF-8");
        conn.getOutputStream().write(bypes);// 输入参数
        InputStream inStream = conn.getInputStream();
        return readInputStream(inStream);
    }
 
    public static byte[] readInputStream(InputStream inStream) throws Exception {
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outStream.write(buffer, 0, len);
        }
        byte[] data = outStream.toByteArray();//网页的二进制数据
        outStream.close();
        inStream.close();
        System.out.println(new String(data, "utf-8"));
        return data;
    }
 
}

 

 

XMLUtil.java
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
 
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
 
/**
 *xml工具类
 *
 */
public class XMLUtil {
 
    /**
     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
     * @param strxml
     * @return
     * @throws JDOMException
     * @throws IOException
     */
    public static Map doXMLParse(String strxml) throws JDOMException, IOException {
        if(null == strxml || "".equals(strxml)) {
            return null;
        }
         
        Map m = new HashMap();
        InputStream in = String2Inputstream(strxml);
        SAXBuilder builder = new SAXBuilder();
        /**********************修复部分内容*********************/
 
        String FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
        builder.setFeature(FEATURE, true);
          
        FEATURE = "http://xml.org/sax/features/external-general-entities";
        builder.setFeature(FEATURE, false);
          
        FEATURE = "http://xml.org/sax/features/external-parameter-entities";
        builder.setFeature(FEATURE, false);
          
        FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
        builder.setFeature(FEATURE, false);
         
        Document doc = builder.build(in);
        Element root = doc.getRootElement();
        List list = root.getChildren();
        Iterator it = list.iterator();
        while(it.hasNext()) {
            Element e = (Element) it.next();
            String k = e.getName();
            String v = "";
            List children = e.getChildren();
            if(children.isEmpty()) {
                v = e.getTextNormalize();
            } else {
                v = XMLUtil.getChildrenText(children);
            }
             
            m.put(k, v);
        }
         
 
        in.close();
         
        return m;
    }
     
    /**
     * 获取子结点的xml
     * @param children
     * @return String
     */
    public static String getChildrenText(List children) {
        StringBuffer sb = new StringBuffer();
        if(!children.isEmpty()) {
            Iterator it = children.iterator();
            while(it.hasNext()) {
                Element e = (Element) it.next();
                String name = e.getName();
                String value = e.getTextNormalize();
                List list = e.getChildren();
                sb.append("<" + name + ">");
                if(!list.isEmpty()) {
                    sb.append(XMLUtil.getChildrenText(list));
                }
                sb.append(value);
                sb.append("</" + name + ">");
            }
        }
         
        return sb.toString();
    }
     
 
    public static InputStream String2Inputstream(String str) {
        return new ByteArrayInputStream(str.getBytes());
    }
 
    public static String mapToXml(SortedMap<Object, Object> sortedMap){
        StringBuffer sb = new StringBuffer("<xml>");
        Iterator iterator = sortedMap.keySet().iterator();
        while (iterator.hasNext()) {
            Object key = (String) iterator.next();
            Object value = sortedMap.get(key);
            sb.append("<"+key+">");
            sb.append("<![CDATA["+value+"]]>");
            sb.append("</"+key+">");
        }
        sb.append("</xml>");
        return sb.toString();
    }
 
}

 

 

 

PaymentConfig.java
public class PaymentConfig {
    /*******微信支付参数*********/
    //公众账号ID
    public static final String appid="";
    //密钥
    public static final String appKey="";
    //商户号
    public static final String mch_id="";
    //接口地址
    public static final String pay_url="https://api.mch.weixin.qq.com/pay/unifiedorder";
    //支付返回地址
    public static final String wxRetrun="";
    //交易场景信息 具体参照微信官方文档不同支付类型的写法
    public static final String scene_info = "{\"h5_info\": {\"type\":\"pc\",\"pc_url\": \"https://pay.qq.com\",\"wap_name\": \"微信支付\"}} ";
     
    public static final int ENROLL_PRICE = 200;
     
}

 

 

 

WeChatPay.java

以下整合了沙箱环境的支付代码:getSignKeyUtils 类可以参考:
import org.jdom.JDOMException;

import java.io.IOException;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

public class WeChatPay {

    /**
     * 二维码支付
     * @param orderNo
     * @param money
     * @param body
     * @param ip
     * @return
     */
    public Map getPaymentMapCode(String orderNo,String money,String body,String ip,String wxReturn){

        String nonce_str=RandomUtil.createRandomString(32);
        //一次签名
        SortedMap<Object, Object> paramMap = new TreeMap<Object, Object>();
        paramMap.put("appid",PaymentConfig.appid);//公众号ID
        paramMap.put("mch_id",PaymentConfig.mch_id);//商户号
        paramMap.put("nonce_str", nonce_str);//32位随机字符串
        paramMap.put("body",body);//商品描述
        paramMap.put("out_trade_no",orderNo);//商户订单号
        paramMap.put("total_fee",money);//设置交易金额 金额为分
        //paramMap.put("total_fee",1);//设置交易金额 金额为分
        paramMap.put("spbill_create_ip",ip);//客户机IP
        paramMap.put("notify_url",wxReturn);//通知地址
        paramMap.put("trade_type","NATIVE");//支付方式 原生扫码
        paramMap.put("product_id", "shangpingid"); //自行定义

        paramMap.put("sign", SignUtil.createSign(paramMap, PaymentConfig.appKey));

        //沙箱环境测试
        //paramMap.put("sign", SignUtil.createSign(paramMap, getSignKeyUtils.getSignKey(nonce_str,PaymentConfig.mch_id,PaymentConfig.appKey)));

        String rXml = "";
        String prepayid="";
        try {
            rXml = new String(XmlPostUtil.sendXmlRequest(PaymentConfig.pay_url, XMLUtil.mapToXml(paramMap)));
            prepayid = (String) XMLUtil.doXMLParse(rXml).get("prepay_id");//得到预支付id
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //二次签名
        SortedMap<Object, Object> paramMap1 = new TreeMap<Object, Object>();
        paramMap1.put("appId", PaymentConfig.appid);
        paramMap1.put("timeStamp", System.currentTimeMillis());
        paramMap1.put("package", "prepay_id="+prepayid);
        paramMap1.put("signType", "MD5");
        String nonceStr2=RandomUtil.createRandomString(32);
        paramMap1.put("nonceStr",nonceStr2);

        paramMap1.put("paySign", SignUtil.createSign(paramMap1, PaymentConfig.appKey));

        //沙箱环境
        //paramMap1.put("paySign", SignUtil.createSign(paramMap1, getSignKeyUtils.getSignKey(nonceStr2,PaymentConfig.mch_id,PaymentConfig.appKey)));

        try {
            Map map = XMLUtil.doXMLParse(rXml);
            System.out.println("return_code:"+map.get("return_code"));
            System.out.println("code_url:"+map.get("code_url"));
            return map;
        } catch (JDOMException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return paramMap1;

    }

    //通知微信正确接收
    public static String getSuccessXml() {
        String xml = "<xml>" +
                "<return_code><![CDATA[SUCCESS]]></return_code>" +
                "<return_msg><![CDATA[OK]]></return_msg>" +
                "</xml>";
        return xml;
    }

}

 

 

 

调用类方法,需要自行完善逻辑代码

public void weixinPay(HttpServletRequest request){
        //请求IP地址
        String ip = request.getRemoteAddr();
        //发起支付
        WeChatPay weChatPay = new WeChatPay();
        //wxReturn 为微信异步回调地址,这里可以根据自己的方式获取
        String wxReturn = PropertyUtils.getPropertyValue(new File(realPathResolver.get(CONFIG)), WEIXIN_NOTICE_URL);

        /**
         * 调用微信支付
         * order.getOrderNo() 订单号
         *  price 订单价格精度转换后的价格   order.getPrice()  订单价格,因为微信是分为单位 所以这里要乘以100   关于支付精度转换,可以查看 
         */

     String price="1";
        Map map = weChatPay.getPaymentMapCode(order.getOrderNo(),price, "微信支付", ip, wxReturn);
        String return_code = String.valueOf(map.get("return_code"));
        if (return_code.equals("SUCCESS")) {
            //微信调用成功
        //code_url是支付的链接
request.getSession().setAttribute("code_url", map.get("code_url") + ""); 
        //跳转到支付页面
} else {
            //微信支付调取失败!
        }
    }

 

QRCodeUtil.java
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.Hashtable;

public class QRCodeUtil {

    private static final String CHARSET = "utf-8";
    private static final String FORMAT_NAME = "JPG";
    // 二维码尺寸
    private static final int QRCODE_SIZE = 300;
    // LOGO宽度
    private static final int WIDTH = 60;
    // LOGO高度
    private static final int HEIGHT = 60;

    private static BufferedImage createImage(String content, String imgPath,
                                             boolean needCompress) throws Exception {
        Hashtable hints = new Hashtable();
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
        hints.put(EncodeHintType.MARGIN, 1);
        BitMatrix bitMatrix = new MultiFormatWriter().encode(content,
                BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, hints);
        int width = bitMatrix.getWidth();
        int height = bitMatrix.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, bitMatrix.get(x, y) ? 0xFF000000
                        : 0xFFFFFFFF);
            }
        }
        if (imgPath == null || "".equals(imgPath)) {
            return image;
        }
        // 插入图片
        QRCodeUtil.insertImage(image, imgPath, needCompress);
        return image;
    }


    private static void insertImage(BufferedImage source, String imgPath,
                                    boolean needCompress) throws Exception {
        File file = new File(imgPath);
        if (!file.exists()) {
            System.err.println(""+imgPath+"   该文件不存在!");
            return;
        }
        Image src = ImageIO.read(new File(imgPath));
        int width = src.getWidth(null);
        int height = src.getHeight(null);
        if (needCompress) { // 压缩LOGO
            if (width > WIDTH) {
                width = WIDTH;
            }
            if (height > HEIGHT) {
                height = HEIGHT;
            }
            Image image = src.getScaledInstance(width, height,
                    Image.SCALE_SMOOTH);
            BufferedImage tag = new BufferedImage(width, height,
                    BufferedImage.TYPE_INT_RGB);
            Graphics g = tag.getGraphics();
            g.drawImage(image, 0, 0, null); // 绘制缩小后的图
            g.dispose();
            src = image;
        }
        // 插入LOGO
        Graphics2D graph = source.createGraphics();
        int x = (QRCODE_SIZE - width) / 2;
        int y = (QRCODE_SIZE - height) / 2;
        graph.drawImage(src, x, y, width, height, null);
        Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
        graph.setStroke(new BasicStroke(3f));
        graph.draw(shape);
        graph.dispose();
    }

    //获取生成二维码的图片流
    public static ByteArrayOutputStream encodeIO(String content,String imgPath,Boolean needCompress) throws Exception {
        BufferedImage image = QRCodeUtil.createImage(content, imgPath,
                needCompress);
        //创建储存图片二进制流的输出流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        //将二进制数据写入ByteArrayOutputStream
        ImageIO.write(image, "jpg", baos);
        return baos;
    }
}

 

 

 

生成二维码请求

@RequestMapping(value = "/get_qr_code")
    public void getQrCode(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //从session中获取前面放在code_url地址

        String content = request.getSession().getAttribute("code_url") + "";
        System.out.printf(content);
        //二维码图片中间logo
        String imgPath = "";
        Boolean needCompress = true;
        //通过调用我们的写的工具类,拿到图片流
        ByteArrayOutputStream out = QRCodeUtil.encodeIO(content, imgPath, needCompress);
        //定义返回参数
        response.setCharacterEncoding("UTF-8");
        response.setContentType("image/jpeg;charset=UTF-8");
        response.setContentLength(out.size());
        ServletOutputStream outputStream = response.getOutputStream();
        outputStream.write(out.toByteArray());
        outputStream.flush();
        outputStream.close();
    }

 

 

支付页面代码、显示二维码,由于微信支付没有同步支付通知,所以需要在这个页面上写轮询方法,查询自己数据库订单,判断是否已经支付

<img src="/get_qr_code"><br/>

 

轮询js  查询订单的方法需要自己写

//2秒轮询一次
setInterval("get_pay_status()",2000);

//轮询订单支付状态
    function get_pay_status() {
        $.ajax({
            url:'/get_order_status',
            data:{
                orderNo:'订单编号'
            },
            success:function (data) {
                if (data.code==1000){
                    //如果后台返回支付成功,则跳转到支付成功页面
                    window.location.href="/to_weixin_return_url.jspx?orderNo=订单编号";
                }
            }
        })
    }

 

 

 

 

 

微信异步回调类,需要自行完善逻辑代码

/**
     * 微信支付通知
     *
     * @return
     */
    @ResponseBody
    @RequestMapping(value = "/wechart_notice")
    public String wechartNotice(HttpServletRequest request, HttpServletResponse response, ModelMap model) {
        String result = "";
        try {
            InputStream inStream = 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();
            result = new String(outSteam.toByteArray(), "utf-8");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //判断返回报文是否为空
        if (StringUtils.isNotBlank(result)) {
            try {
                Map<String, String> map = XMLUtil.doXMLParse(result);
                //获取商家订单编号 对应orderNo
                String orderNo = map.get("out_trade_no");
//获取微信支付订单号
                String transaction_id = map.get("transaction_id");

                Order order = orderMng.findByOrderNo(orderNo);
                //判断支付是否成功
                if ("SUCCESS".equals(map.get("result_code"))) {
                    //支付成功,这里之所以加了一个判断,是因为这个回调可能会有多次,所以我们只有当订单状态时未支付成功的情况下,才执行下面操作
                    if (!Constants.ORDER_SUCCESS.equals(order.getStatus())) {
                        //当微信支付成功后,把订单支付状态改为已支付
                        order.setStatus(Constants.ORDER_SUCCESS);
                    }
                    //处理业务逻辑
                } else {
                    //支付失败
                    order.setStatus(Constants.ORDER_FAIL);
                }
//更新数据库订单状态
                orderMng.update(order);
            } catch (JDOMException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }return WeChatPay.getSuccessXml();
    }