微信公众号开发《五》基于Java实现微信支付(公众号支付)简单教程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/liaohaojian/article/details/79044098

最近公司需求,需要在微信公众号内完成支付,找到官方文档,文档还可以,讲的也挺详细,不过有一个地方很坑爹,就是微信内H5调起支付需要一个签名,而他给出的参考签名方式跟统一下单签名一致,害的我以为,他这个签名就是统一下单那个签名,后面找了很久看了好多博客才明白这个签名是怎么生成的(JS-SDK中微信支付有说明)。弄了半天,汗颜。下面进入正题。

微信支付分为很多种,有刷卡支付,公众号支付,扫码支付,APP支付,H5支付,小程序支付,本教程只讲解公众号支付。主要分三大块来讲解。

1.公众号支付介绍

微信支付只面向已认证客户,需要申请,申请成功后有个商家号,还要一个API密匙即key(微信商户平台(pay.weixin.qq.com)-->账户中心-->账户设置-->API安全-->密钥设置)。而公众号支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付。应用场景有:
◆ 用户在微信公众账号内进入商家公众号,打开某个主页面,完成支付
◆ 用户的好友在朋友圈、聊天窗口等分享商家页面连接,用户点击链接打开商家页面,完成支付
◆ 将商户页面转换成二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付

2.开发流程

在这里贴出官方文档地址https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1。有兴趣的可以研究研究,贴出最主要的业务流程图:
啥,看不下去,那简单来说,你要掌握三点:

1.在商家后台设置支付目录,设置路径:商户平台-->产品中心-->开发配置。填写你项目所在域名

2.在公众号后台设置授权域名,因为在统一下单接口中要求必传用户openid,而获取openid则需要您在公众平台设置获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,否则将获取失败。

3.调通统一下单支付结果通知接口(详细看demo)

tip:如果不知道如何获取openid,可以看本人另外一篇博客微信公众号开发《一》OAuth2.0网页授权认证获取用户的详细信息,实现自动登陆

3.demo展示

demo使用了包:xstream-1.3.1.jar,jdom.jar,用于把数据封装成xml格式与xml解析。首先我们把请求参数封装成类
/**
 * 统一下单请求参数
 * @author lhao
 *
 */
public class UnifiedOrderRequest {
	//变量名	字段名	必填	 	类型		示例值	描述
	private String appid;//	公众账号ID   是		String(32)	wxd678efh567hg6787	微信支付分配的公众账号ID(企业号corpid即为此appId)
	private String mch_id;//商户号  必填	String(32)	1230000109	微信支付分配的商户号
	private String device_info;	//设备号	否		String(32)	013467007045764	自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB"
	private String nonce_str;//随机字符串	是 	     String(32)	5K8264ILTKCH16CQ2502SI8ZNMTM67VS	随机字符串,长度要求在32位以内。推荐随机数生成算法
	private String sign;//签名	是 	     String(32)	  C380BEC2BFD727A4B6845133519F3AD6	通过签名算法计算得出的签名值,详见签名生成算法
	private String sign_type;//签名类型	sign_type	否	String(32)	HMAC-SHA256	签名类型,默认为MD5,支持HMAC-SHA256和MD5。
	private String body;//商品描述	body 是	 String(128)	腾讯充值中心-QQ会员充值  商品简单描述,该字段请按照规范传递,具体请见参数规定	
	private String detail;//商品详情	detail	否	String(6000)	 	单品优惠字段(暂未上线)
	private String attach;//附加数据	attach	否	String(127)	深圳分店	附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
	private String out_trade_no;//商户订单号	out_trade_no	是	String(32)	20150806125346	商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。详见商户订单号
	private String fee_type;//标价币种	fee_type	否	String(16)	CNY	符合ISO 4217标准的三位字母代码,默认人民币:CNY,详细列表请参见货币类型
	private String total_fee;//标价金额	total_fee	是	Int	88	订单总金额,单位为分,详见支付金额
	private String spbill_create_ip;//终端IP	spbill_create_ip	是	String(16)	123.12.12.123	APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
	private String time_start;//交易起始时间	time_start	否	String(14)	20091225091010	订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则
	private String time_expire;//交易结束时间	time_expire	否	String(14)	20091227091010	订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则   注意:最短失效时间间隔必须大于5分钟
	private String goods_tag;//订单优惠标记	goods_tag	否	String(32)	WXG	订单优惠标记,使用代金券或立减优惠功能时需要的参数,说明详见代金券或立减优惠
	private String notify_url;//通知地址	notify_url	是	String(256)	http://www.weixin.qq.com/wxpay/pay.php	异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
	private String trade_type;//交易类型	trade_type	是	String(16)	JSAPI	取值如下:JSAPI,NATIVE,APP等,说明详见参数规定
	private String product_id;//商品ID	product_id	否	String(32)	12235413214070356458058	trade_type=NATIVE时(即扫码支付),此参数必传。此参数为二维码中包含的商品ID,商户自行定义。
	private String limit_pay;//指定支付方式	limit_pay	否	String(32)	no_credit	上传此参数no_credit--可限制用户不能使用信用卡支付
	private String openid;//用户标识	openid	否	String(128)	oUpF8uMuAJO_M2pxb1Q9zNjWeS6o	trade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识。openid如何获取,可参考【获取openid】。企业号请使用【企业号OAuth2.0接口】获取企业号内成员userid,再调用【企业号userid转openid接口】进行转换
	
	//省略get,set方法
}
/**
 * 统一下单返回参数
 * @author lhao
 *
 */
public class UnifiedOrderRespose {
    private String return_code;             //返回状态码
    private String return_msg;              //返回信息
    private String appid;                   //公众账号ID
    private String mch_id;                  //商户号
    private String device_info;             //设备号
    private String nonce_str;               //随机字符串
    private String sign;                    //签名
    private String result_code;             //业务结果
    private String err_code;                //错误代码
    private String err_code_des;            //错误代码描述
    private String trade_type;              //交易类型
    private String prepay_id;               //预支付交易会话标识
    private String code_url;                //二维码链接
    
    //省略get/set方法
}
/**
 * 常量
 */
public class WXPayConstants {

    public enum SignType {
        MD5, HMACSHA256
    }
    public static final String FAIL     = "FAIL";
    public static final String SUCCESS  = "SUCCESS";
    public static final String HMACSHA256 = "HMAC-SHA256";
    public static final String MD5 = "MD5";
    public static final String FIELD_SIGN = "sign";
    public static final String FIELD_SIGN_TYPE = "sign_type";

}
MD5加密工具类,封装参数、请求工具类
import java.security.MessageDigest;
/**
 * MD5工具类
 * @author lh
 */
public class MD5Util {
    public final static String MD5(String s) {
        char hexDigits[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};       

        try {
            byte[] btInput = s.getBytes();
            // 鑾峰緱MD5鎽樿绠楁硶鐨�MessageDigest 瀵硅�?
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // 浣跨敤鎸囧畾鐨勫瓧鑺傛洿鏂版憳瑕�?
            mdInst.update(btInput);
            // 鑾峰緱�?�嗘�?
            byte[] md = mdInst.digest();
            // 鎶婂瘑鏂囪浆鎹㈡垚鍗佸叚杩涘埗鐨勫瓧绗︿覆褰㈠紡
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            String md5Str = new String(str); 
            return md5Str;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import java.security.MessageDigest;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
import wp.WXPayConstants.SignType;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.lang.RandomStringUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 支付工具类
 * @author lh
 */
public class WXPayUtil {
	private static Logger log = LoggerFactory.getLogger(WXPayUtil.class);
	
	/**
	 * 生成订单对象信息
	 * @param orderId 订单号
	 * @param appId 微信appId
	 * @param mch_id 微信分配的商户ID
	 * @param body  支付介绍主体
	 * @param price 支付价格(放大100倍)
	 * @param spbill_create_ip 终端IP
	 * @param notify_url  异步直接结果通知接口地址
	 * @param noncestr 
	 * @return
	 */
	public static Map<String,Object> createOrderInfo(Map<String, String> requestMap) {  
	    //生成订单对象  
	    UnifiedOrderRequest unifiedOrderRequest = new UnifiedOrderRequest();  
	    unifiedOrderRequest.setAppid(requestMap.get("appId"));//公众账号ID  
	    unifiedOrderRequest.setBody(requestMap.get("body"));//商品描述  
	    unifiedOrderRequest.setMch_id(requestMap.get("mch_id"));//商户号  
	    unifiedOrderRequest.setNonce_str(requestMap.get("noncestr"));//随机字符串    
	    unifiedOrderRequest.setNotify_url(requestMap.get("notify_url"));//通知地址  
	    unifiedOrderRequest.setOpenid(requestMap.get("userWeixinOpenId"));
	    unifiedOrderRequest.setDetail(requestMap.get("detail"));//详情
	    unifiedOrderRequest.setOut_trade_no(requestMap.get("out_trade_no"));//商户订单号  
	    unifiedOrderRequest.setSpbill_create_ip(requestMap.get("spbill_create_ip"));//终端IP  
	    unifiedOrderRequest.setTotal_fee(requestMap.get("payMoney"));  //金额需要扩大100倍:1代表支付时是0.01  
	    unifiedOrderRequest.setTrade_type("JSAPI");//JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付
	    SortedMap<String, String> packageParams = new TreeMap<String, String>();  
	    packageParams.put("appid", unifiedOrderRequest.getAppid());  
	    packageParams.put("body", unifiedOrderRequest.getBody());  
	    packageParams.put("mch_id", unifiedOrderRequest.getMch_id());  
	    packageParams.put("nonce_str", unifiedOrderRequest.getNonce_str());  
	    packageParams.put("notify_url", unifiedOrderRequest.getNotify_url());
	    packageParams.put("openid", unifiedOrderRequest.getOpenid());
	    packageParams.put("detail", unifiedOrderRequest.getDetail());
	    packageParams.put("out_trade_no", unifiedOrderRequest.getOut_trade_no());  
	    packageParams.put("spbill_create_ip", unifiedOrderRequest.getSpbill_create_ip());  
	    packageParams.put("total_fee", unifiedOrderRequest.getTotal_fee());  
	    packageParams.put("trade_type", unifiedOrderRequest.getTrade_type());  
	    try {
			unifiedOrderRequest.setSign(generateSignature(packageParams,"你的密匙"));//签名
		} catch (Exception e) {
			e.printStackTrace();
		}
	    //将订单对象转为xml格式  
	    xstream.alias("xml", UnifiedOrderRequest.class);//根元素名需要是xml  
	    System.out.println("封装好的统一下单请求数据:"+xstream.toXML(unifiedOrderRequest).replace("__", "_"));
	    Map<String,Object> responseMap = new HashMap<String,Object>();
	    responseMap.put("orderInfo_toString", xstream.toXML(unifiedOrderRequest).replace("__", "_"));
	    responseMap.put("unifiedOrderRequest",unifiedOrderRequest);
	    return responseMap;  
	} 
	
	/** 
	 * 生成签名 
	 * @param appid_value 
	 * @param mch_id_value 
	 * @param productId 
	 * @param nonce_str_value 
	 * @param trade_type  
	 * @param notify_url  
	 * @param spbill_create_ip  
	 * @param total_fee  
	 * @param out_trade_no  
	 * @return 
	 */  
	private static String createSign(UnifiedOrderRequest unifiedOrderRequest) {  
	    //根据规则创建可排序的map集合  
	    SortedMap<String, String> packageParams = new TreeMap<String, String>();  
	    packageParams.put("appid", unifiedOrderRequest.getAppid());  
	    packageParams.put("body", unifiedOrderRequest.getBody());  
	    packageParams.put("mch_id", unifiedOrderRequest.getMch_id());  
	    packageParams.put("nonce_str", unifiedOrderRequest.getNonce_str());  
	    packageParams.put("notify_url", unifiedOrderRequest.getNotify_url());  
	    packageParams.put("out_trade_no", unifiedOrderRequest.getOut_trade_no());  
	    packageParams.put("spbill_create_ip", unifiedOrderRequest.getSpbill_create_ip());  
	    packageParams.put("trade_type", unifiedOrderRequest.getTrade_type());  
	    packageParams.put("total_fee", unifiedOrderRequest.getTotal_fee());  
	    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 + "&");  
	        }  
	    }  
	    //第二步拼接key,key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置  
	    sb.append("key="+"你的密匙");  
	    String sign = MD5Util.MD5(sb.toString()).toUpperCase();//MD5加密  
	    log.error("方式一生成的签名="+sign);
	    return sign;  
	}
	
	private static XStream xstream = new XStream(new XppDriver() {  
        public HierarchicalStreamWriter createWriter(Writer out) {  
            return new PrettyPrintWriter(out) {  
                // 对所有xml节点的转换都增加CDATA标记  
                boolean cdata = true;  
                String NodeName = "";
                @SuppressWarnings("unchecked")  
                public void startNode(String name, Class clazz) {  
                	NodeName = name;
                    super.startNode(name, clazz);  
                }  
                protected void writeText(QuickWriter writer, String text) {  
                    if (cdata) {  
                    	if(!NodeName.equals("detail")){
                    		writer.write(text); 
                    	}else{
                    		writer.write("<![CDATA[");  
                            writer.write(text);  
                            writer.write("]]>"); 
                    	}
                    } else {  
                        writer.write(text);  
                    }  
                }  
            };  
        }  
    }); 
	
	//xml解析    
    public static SortedMap<String, String> doXMLParseWithSorted(String strxml) throws Exception {    
          strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");    
          if(null == strxml || "".equals(strxml)) {    
              return null;    
          }    
          SortedMap<String,String> m = new TreeMap<String,String>();     
          InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));    
          SAXBuilder builder = new SAXBuilder();    
          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 = getChildrenText(children);    
              }    
              m.put(k, v);    
          }    
          //关闭流    
          in.close();     
          return m;    
    }   
    
    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(getChildrenText(list));    
                }    
                sb.append(value);    
                sb.append("</" + name + ">");    
            }    
        }     
        return sb.toString();    
	} 
	/** 
	 * 调统一下单API 
	 * @param orderInfo 
	 * @return 
	 */  
	public static UnifiedOrderRespose httpOrder(String orderInfo) {  
	    String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";  
	    try {  
	        HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();  
	        //加入数据    
			conn.setRequestMethod("POST");    
			conn.setDoOutput(true);    
			BufferedOutputStream buffOutStr = new BufferedOutputStream(conn.getOutputStream());    
			buffOutStr.write(orderInfo.getBytes("UTF-8"));  
			buffOutStr.flush();    
			buffOutStr.close();    
			//获取输入流    
			BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));    
			String line = null;    
			StringBuffer sb = new StringBuffer();    
			while((line = reader.readLine())!= null){    
				sb.append(line);    
			}    
			//将请求返回的内容通过xStream转换为UnifiedOrderRespose对象  
			xstream.alias("xml", UnifiedOrderRespose.class);  
			UnifiedOrderRespose unifiedOrderRespose = (UnifiedOrderRespose)xstream.fromXML(sb.toString());  
			return unifiedOrderRespose;
	    } catch (Exception e) {  
	        e.printStackTrace();  
	    }  
	    return null;  
	}  
    /**
     * XML格式字符串转换为Map
     *
     * @param strXML XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
                // do nothing
            }
            return data;
        } catch (Exception ex) {
            WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
            throw ex;
        }

    }

    /**
     * 将Map转换为XML格式的字符串
     *
     * @param data Map类型数据
     * @return XML格式的字符串
     * @throws Exception
     */
    public static String mapToXml(Map<String, String> data) throws Exception {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
        org.w3c.dom.Document document = documentBuilder.newDocument();
        org.w3c.dom.Element root = document.createElement("xml");
        document.appendChild(root);
        for (String key: data.keySet()) {
            String value = data.get(key);
            if (value == null) {
                value = "";
            }
            value = value.trim();
            org.w3c.dom.Element filed = document.createElement(key);
            filed.appendChild(document.createTextNode(value));
            root.appendChild(filed);
        }
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        DOMSource source = new DOMSource(document);
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        StringWriter writer = new StringWriter();
        StreamResult result = new StreamResult(writer);
        transformer.transform(source, result);
        String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
        try {
            writer.close();
        }
        catch (Exception ex) {
        }
        return output;
    }


    /**
     * 生成带有 sign 的 XML 格式字符串
     *
     * @param data Map类型数据
     * @param key API密钥
     * @return 含有sign字段的XML
     */
    public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
        return generateSignedXml(data, key, SignType.MD5);
    }

    /**
     * 生成带有 sign 的 XML 格式字符串
     *
     * @param data Map类型数据
     * @param key API密钥
     * @param signType 签名类型
     * @return 含有sign字段的XML
     */
    public static String generateSignedXml(final Map<String, String> data, String key, SignType signType) throws Exception {
        String sign = generateSignature(data, key, signType);
        data.put(WXPayConstants.FIELD_SIGN, sign);
        return mapToXml(data);
    }


    /**
     * 判断签名是否正确
     *
     * @param xmlStr XML格式数据
     * @param key API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
        Map<String, String> data = xmlToMap(xmlStr);
        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
            return false;
        }
        String sign = data.get(WXPayConstants.FIELD_SIGN);
        return generateSignature(data, key).equals(sign);
    }

    /**
     * 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
     *
     * @param data Map类型数据
     * @param key API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
        return isSignatureValid(data, key, SignType.MD5);
    }

    /**
     * 判断签名是否正确,必须包含sign字段,否则返回false。
     *
     * @param data Map类型数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception {
        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
            return false;
        }
        String sign = data.get(WXPayConstants.FIELD_SIGN);
        return generateSignature(data, key, signType).equals(sign);
    }

    /**
     * 生成签名
     *
     * @param data 待签名数据
     * @param key API密钥
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key) throws Exception {
        return generateSignature(data, key, SignType.MD5);
    }

    /**
     * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
     *
     * @param data 待签名数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals(WXPayConstants.FIELD_SIGN)) {
                continue;
            }
            if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("key=").append(key);
        if (SignType.MD5.equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        }
        else if (SignType.HMACSHA256.equals(signType)) {
            return HMACSHA256(sb.toString(), key);
        }
        else {
        	log.error("获取签名失败,失败原因:"+String.format("Invalid sign_type: %s", signType));
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }


    /**
     * 获取随机字符串 Nonce Str
     * @return String 随机字符串
     */
    public static String generateNonceStr() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }
    /** 
     * Map转xml数据 
     */  
    public static String GetMapToXML(Map<String,String> param){  
        StringBuffer sb = new StringBuffer();  
        sb.append("<xml>");  
        for (Map.Entry<String,String> entry : param.entrySet()) {   
        	sb.append("<"+ entry.getKey() +">");  
        	sb.append(entry.getValue());  
        	sb.append("</"+ entry.getKey() +">");  
        }    
        sb.append("</xml>");  
        return sb.toString();  
    }  

    /**
     * 生成 MD5
     * @param data 待处理数据
     * @return MD5结果
     */
    public static String MD5(String data) throws Exception {
        java.security.MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 生成 HMACSHA256
     * @param data 待处理数据
     * @param key 密钥
     * @return 加密结果
     * @throws Exception
     */
    public static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 日志
     * @return
     */
    public static Logger getLogger() {
        Logger logger = LoggerFactory.getLogger("wxpay java sdk");
        return logger;
    }

    /**
     * 获取当前时间戳,单位秒
     * @return
     */
    public static long getCurrentTimestamp() {
        return System.currentTimeMillis()/1000;
    }

    /**
     * 获取当前时间戳,单位毫秒
     * @return
     */
    public static long getCurrentTimestampMs() {
        return System.currentTimeMillis();
    }

    /**
     * 生成 uuid, 即用来标识一笔单,也用做 nonce_str
     * @return
     */
    public static String generateUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }

    /** 
     * 支付签名 
     * @param timestamp 
     * @param noncestr 
     * @param packages 
     * @return 
     * @throws UnsupportedEncodingException  
     */  
    public static String paySign(String timestamp, String noncestr,String packages,String appId){  
        Map<String, String> paras = new HashMap<String, String>();  
        paras.put("appid", appId);  
        paras.put("timestamp", timestamp);  
        paras.put("noncestr", noncestr);  
        paras.put("package", packages);  
        paras.put("signType", "MD5");  
	    StringBuffer sb = new StringBuffer();  
	    Set es = paras.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 + "&");  
	        }  
	    }  
	    String sign = MD5Util.MD5(sb.toString()).toUpperCase();//MD5加密  
        return sign;  
    }  
}
一些准备就绪,下面我们看看调用方法
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.photovoltaic.model.ResultEnum;
import com.photovoltaic.model.UserOrderInfo;
import com.photovoltaic.model.UserPayInfo;
import com.photovoltaic.util.publicUtil;
import com.photovoltaic.weixin.model.TemplateData;
import com.photovoltaic.weixin.model.TimedTask;
import com.photovoltaic.weixin.model.UnifiedOrderRequest;
import com.photovoltaic.weixin.model.UnifiedOrderRespose;
import com.photovoltaic.weixin.model.WxTemplate;
import com.photovoltaic.weixin.util.WXPayUtil;
import com.photovoltaic.weixin.util.WeixinUtil;

/**
 * 微信支付controller
 * 1.用户发起微信支付,初始化数据、调用统一下单接口。生成JSAPI页面调用的支付参数并签名(paySign,prepay_id,nonceStr,timestamp)
 * 2.js如果返回Ok,提示支付成功,实际支付结果已收到通知为主。
 * 3.在微信支付结果通知中,获取微信提供的最终用户支付结果信息,支付结果等信息更新用户支付记录中
 * 4.根据微信支付结果通知中的微信订单号调用查询接口,如果查询是已经支付成功,则发送支付成功模板信息给客户
 * @author hl
 *
 */
@Controller
@RequestMapping(value = "/pay")
public class WXPayController extends WeixinBaseController{
	private static Logger log = LoggerFactory.getLogger(WXPayController.class);
	/**
	 * 获取终端IP
	 * @param request
	 * @return
	 */
	public static String getIpAddr(HttpServletRequest request)  {  
		String ip  =  request.getHeader( " x-forwarded-for " );  
		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.getRemoteAddr();  
		}   
		return  ip;  
	 } 
	/**
	 * 支付初始化
	 * @param payMoney
	 * @return
	 */
	@RequestMapping("/toPayInit")
	@ResponseBody
	public Map<String,Object> toPay(@RequestParam(value="payMoney",required=true) String payMoney,@RequestParam(value="userWeixinOpenId",required=true) String userWeixinOpenId){
		Map<String,Object> map = new HashMap<>();
		String orderId = String.valueOf(WXPayUtil.generateUUID());
		String noncestr = WXPayUtil.generateNonceStr();
		Map<String,String> requestMap = new HashMap<String, String>();
		requestMap.put("appId", "wx3fda6310becfe801");
		requestMap.put("userWeixinOpenId",userWeixinOpenId);
		requestMap.put("out_trade_no",orderId);
		requestMap.put("mch_id", "商家号");
		requestMap.put("payMoney",payMoney);
		requestMap.put("spbill_create_ip", getIpAddr(request));
		requestMap.put("notify_url", "支付结果回调通知路径");
		requestMap.put("noncestr", noncestr);
		requestMap.put("body","一元联系");
		requestMap.put("detail","获取电站用户的联系方式");
		Map<String,Object> requestInfo = WXPayUtil.createOrderInfo(requestMap);
		String orderInfo_toString = (String) requestInfo.get("orderInfo_toString");
		 //判断返回码
		UnifiedOrderRespose orderResponse = WXPayUtil.httpOrder(orderInfo_toString);// 调用统一下单接口
		//根据微信文档return_code 和result_code都为SUCCESS的时候才会返回code_url  
		if(null!=orderResponse  && "SUCCESS".equals(orderResponse.getReturn_code()) && "SUCCESS".equals(orderResponse.getResult_code())){  
			String timestamp = String.valueOf(WXPayUtil.getCurrentTimestamp());
			map.put("timestamp",timestamp);
			map.put("noncestr",noncestr);
			UnifiedOrderRequest unifiedOrderRequest = (UnifiedOrderRequest) requestInfo.get("unifiedOrderRequest");
			map.put("unifiedOrderRequest",unifiedOrderRequest);
			SortedMap<String, String> packageParams = new TreeMap<String, String>();  
			packageParams.put("appId","你的appId");  
		    packageParams.put("signType","MD5");  
		    packageParams.put("nonceStr", noncestr);  
		    packageParams.put("timeStamp", timestamp);  
		    String packages = "prepay_id="+orderResponse.getPrepay_id();
		    packageParams.put("package",packages);  
			String sign = null;//这个梗,就是开头说的,弄了半天才弄出来的
			try {
				sign = WXPayUtil.generateSignature(packageParams,"你的密匙");
			} catch (Exception e) {
				map.put("result",-1);
				e.printStackTrace();
			}
			if(sign!=null && !"".equals(sign)){
				map.put("paySign",sign);
				map.put("result",1);
			}else{
				map.put("result",-1);
			}
			map.put("prepay_id",orderResponse.getPrepay_id());
		    return map;  
		}else{ //不成功
			String text = "调用微信支付出错,返回状态码:"+orderResponse.getReturn_code()+",返回信息:"+orderResponse.getReturn_msg();
			if(orderResponse.getErr_code()!=null && !"".equals(orderResponse.getErr_code())){
				text = text +",错误码:"+orderResponse.getErr_code()+",错误描述:"+orderResponse.getErr_code_des();
			}
			log.error(text);
			map.put("result",-1);
		    return map;  
		}
	}
	
	/**
	 * 异步回调接口
	 * @param request
	 * @param response
	 * @throws Exception
	 */
	@RequestMapping(value="/paymentNotice",produces="text/html;charset=utf-8")
	@ResponseBody
	public String WeixinParentNotifyPage(HttpServletRequest request,HttpServletResponse response) throws Exception{
		ServletInputStream instream = request.getInputStream();
		StringBuffer sb = new StringBuffer();
		int len = -1;
		byte[] buffer = new byte[1024];
		while((len = instream.read(buffer)) != -1){
			sb.append(new String(buffer,0,len));
		}
		instream.close();
//		log.error("支付通知回调信息:"+sb.toString());
//		Map<String,String> map = WXPayUtil.doXMLParseWithSorted(sb.toString());//接受微信的通知参数
		Map<String,String> map = WXPayUtil.xmlToMap(sb.toString());//接受微信的回调的通知参数
		Map<String,String> return_data = new HashMap<String,String>();
		//判断签名是否正确
		if(WXPayUtil.isSignatureValid(map, "你的密匙")){
			if(map.get("return_code").toString().equals("FAIL")){
				return_data.put("return_code", "FAIL");
				return_data.put("return_msg", map.get("return_msg"));
			}else if(map.get("return_code").toString().equals("SUCCESS")){
				String result_code = map.get("result_code").toString();
				String out_trade_no = map.get("out_trade_no").toString();
				//获得你自己的订单详情
				UserPayInfo payInfo = wxPayService.getUserPayInfo(out_trade_no);
				if(payInfo == null){
					return_data.put("return_code", "FAIL");
					return_data.put("return_msg", "订单不存在");
					return WeixinUtil.GetMapToXML(return_data);
				}else{
					//2	已支付(不确定是否支付成功)3 支付完成  4 取消支付	 5支付失败
					if(result_code.equals("SUCCESS")){//支付成功
						//如果订单已经支付直接返回成功
						if(payInfo.getPayStatus()==3){
							return_data.put("return_code", "SUCCESS");
							return_data.put("return_msg", "OK");
							return WXPayUtil.GetMapToXML(return_data);
						}else{
							String sign = map.get("sign").toString();
							String total_fee = map.get("total_fee").toString();//订单金额
							if(!publicUtil.subZeroAndDot3(payInfo.getTotal_fee().toString()).equals(total_fee)){//订单金额是否一致
								return_data.put("return_code", "FAIL");
								return_data.put("return_msg", "金额异常");
							}else{
								String time_end = map.get("time_end").toString();
								String bank_type = map.get("bank_type").toString();
								String settlement_total_fee = map.get("settlement_total_fee");
								if(settlement_total_fee==null || "".equals(settlement_total_fee)){
									settlement_total_fee = "0";
								}
								payInfo.setSign(sign);
								payInfo.setResult_code(result_code);
								payInfo.setPayStatus(3);
								payInfo.setTime_end(time_end);
								payInfo.setSettlement_total_fee(settlement_total_fee);
								payInfo.setBank_type(bank_type);
								payInfo.setCoupon_fee("0");
								int result = wxPayService.updatePayInfo(payInfo);
								if(result<=0){
									return_data.put("return_code", "FAIL");
									return_data.put("return_msg", "更新订单失败");
									return WeixinUtil.GetMapToXML(return_data);
								}else{
									UserOrderInfo orderInfo = new UserOrderInfo();
									orderInfo.setId(payInfo.getOrderId());
									orderInfo.setStatus(2);
									result = wxPayService.updateOrderInfo(orderInfo);
									if(result<=0){
										return_data.put("return_code", "FAIL");
										return_data.put("return_msg", "更新订单失败");
										return WeixinUtil.GetMapToXML(return_data);
									}else{
										return_data.put("return_code", "SUCCESS");
										return_data.put("return_msg", "OK");
										return WeixinUtil.GetMapToXML(return_data);
									}
								}
							}
						}
					}else{//支付失败,更新支付结果
						if(payInfo!=null){
							payInfo.setResult_code(result_code);
							payInfo.setPayStatus(5);
							payInfo.setErr_code(map.get("err_code").toString());
							payInfo.setErr_code_des(map.get("err_code_des").toString());
							wxPayService.updatePayInfo(payInfo);
						}
						return_data.put("return_code", "FAIL");
						return_data.put("return_msg",map.get("return_msg").toString());
						return WeixinUtil.GetMapToXML(return_data);
					}
				}
			}
		}else{
			return_data.put("return_code", "FAIL");
			return_data.put("return_msg", "签名错误");
		}
		String xml = WXPayUtil.GetMapToXML(return_data);
		log.error("支付通知回调结果:"+xml);
		return xml;
	}
}
前端唤起微信支付,看jsp代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
<c:set var="BS" value="${pageContext.request.contextPath}"></c:set>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>支付页面</title>
<script type="text/javascript" src="${BS}/js/jquery-1.7.2.min.js"></script>
</head>
<input type="hidden" id="weixinOperId" value="用户operId">
<script type="text/javascript">
var phoneWidth = parseInt(window.screen.width);
var phoneScale = phoneWidth/640;
var ua = navigator.userAgent;
if (/Android (\d+\.\d+)/.test(ua)){
    var version = parseFloat(RegExp.$1);
    if(version>2.3){
        document.write("<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0, minimum-scale = 1.0, maximum-scale = "+phoneScale+", target-densitydpi=device-dpi\">");
    }else{
        document.write("<meta name=\"viewport\" content=\"width=device-width, target-densitydpi=device-dpi\">");
    }
}else{
    document.write("<meta name=\"viewport\" content=\"width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no,target-densitydpi=device-dpi\">");
}

function initPay(powerStationId,userId){
	if(!is_weixin()){
		alert("请在微信客户端打开该链接");
	}else if(!weixin_version()){
		alert("你微信版本太低,不支持微信支付功能,请先更新你的微信");
	}else{
		if(typeof WeixinJSBridge == "undefined"){
			if( document.addEventListener ){
				document.addEventListener('WeixinJSBridgeReady', initPay, false);
			}else if (document.attachEvent){
				document.attachEvent('WeixinJSBridgeReady', initPay); 
				document.attachEvent('onWeixinJSBridgeReady', initPay);
			}
		}else{
			toPay(powerStationId,userId);
		}
	}
}

function toPay(powerStationId,userId){
	$.ajax({
		url : "/"+getProjectName()+"/pay/toPayInit",
		type:"POST",
		dataType : 'json', // 服务器返回的格式,可以是json或xml等
		data:{
			payMoney:1,
			userWeixinOpenId:'用户operId'
		},
		success : function(result) { // 服务器响应成功时的处理函数
			if(result.result==1){//插入支付记录
				var paySign = result.paySign;
				var prepay_id = result.prepay_id;
				var nonceStr = result.noncestr;
				var timestamp = result.timestamp;
				var unifiedOrderRequest = result.unifiedOrderRequest;
				var spbill_create_ip = unifiedOrderRequest.spbill_create_ip;
				var detail = unifiedOrderRequest.detail;
				var out_trade_no = unifiedOrderRequest.out_trade_no;
				$.ajax({
					url : "/"+getProjectName()+"/pay/toSavePayInfo",
					type:"POST",
					dataType : 'json', // 服务器返回的格式,可以是json或xml等
					data:{
						spbill_create_ip:spbill_create_ip,
						detail:detail,
						out_trade_no:out_trade_no,
						total_fee:1,
						powerStationId:powerStationId,
						userId:userId,
						order_type:1
					},
					success : function(result) { // 服务器响应成功时的处理函数
						if(result>0){//插入支付记录
							onBridgeReady(paySign,prepay_id,nonceStr,timestamp);
						}
					},
					error : function(data, status, e) { // 服务器响应失败时的处理函数
						$.toptip("系统出错,请联系系统运营商", 'error');
					}
				});
			}else{
				$.toptip("初始化支付接口失败,请联系系统运营商", 'error');
			}
		},
		error : function(data, status, e) { // 服务器响应失败时的处理函数
			$.toptip("初始化支付接口失败,请联系系统运营商", 'error');
		}
	});
}

function onBridgeReady(paySign,prepay_id,nonceStr,timestamp){
   var weixinOperId = $("#weixinOperId").val();
   WeixinJSBridge.invoke(
       'getBrandWCPayRequest', {
           "appId":weixinOperId,     //公众号名称,由商户传入     
           "timeStamp":timestamp,         //时间戳,自1970年以来的秒数     
           "nonceStr":nonceStr, //随机串     
           "package":"prepay_id="+prepay_id,     
           "signType":"MD5",         //微信签名方式:     
           "paySign":paySign //微信签名 (这个签名获取看后台)
       },
       function(res){
    		// 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回    ok,但并不保证它绝对可靠。 
           if(res.err_msg == "get_brand_wcpay_request:ok" ) {
        	   alert("支付成功", 'success');
           }else if(res.err_msg == "get_brand_wcpay_request:cancel" ) {
        	   alert("用户取消", 'success');
           }    
       }
   ); 
}
</script>
<body>
	<div  id="addressArea" style="min-height:526px;">
		<section class="SelectCityWrap" style="width:98%;">
		    <section class="content">
				<div class="nav">
			    	<a class=""  nav="nav_1" οnclick="initPay(11,1)">马上支付</a>
			    </div>
			</section>
		</section>
	</div>
</body>
</html>
到此全部搞定,3个封装数据类,2个工具类,一个controller,一个jsp调用,jsp结果操作提示是用jQuery WeUI插件,很不错的一个手机网站开发插件。
如有不解或者逻辑不通的地方,欢迎评论区留言。
最后附上微信开发系列的目录链接玩转微信公众号开发目录-持续更新






展开阅读全文

没有更多推荐了,返回首页