前言

前段时间在做一个微信公众号,需要用到微信支付的接口,第一次用,果然和前面写其他接口经历一样,好繁琐的操作。去找了相关资料,看了看文档,越看越复杂,大多数博客上面的总是缺着一部分跑不起来,官方demo又好复杂,索性自己好好搞一次。

需求准备

1.首先一个公众号(服务号),也可以企业号,订阅号没有支付权限(权限可以查文档),可以去微信公众平台申请,而且需要认证,一年300
2.一个域名,必须经过ICP备案,必须备案,很重要,也就是说还需要一个服务器,备案大概1个月
3.微信支付开发文档
4.商户平台账号,也就是微信支付平台,前段时间微信支付单独迁移到了商户平台,所以需要单独申请

实现过程

1.前提公众号的配置已经完成,比如在公众平台的服务器配置,接口域名配置,授权配置等等。还有商户平台的支付授权目录填写。这些都可以通过文档或者搜索引擎找到。
2.接下来需要调用统一下单接口获取预支付id(prepay_id)
其中需要很多参数,参数的具体含义可见文档
这是签名生成算法大多数错误都是由于签名错误

//微信支付
String nonceStr = "2D8264ILTKCH16CQ2502SI8ZNMTM67VS";//随机字符串,可以自己生成
String MCHID = "11111111";    //商户号,商户平台注册
String WX_APPID = ;  //APPID,也就是公众平台APPID
String body = "xx";  //商品描述
String WX_PAY_CALLBACK = "";//异步接收微信支付结果通知的回调地址
String ip = request.getRemoteAddr(); //用户端ip
String orderSn = String.valueOf((int)(new Date().getTime()));//商户订单号,这用的是时间
String relAmount = 1;//订单总金额,单位为分
String openid = user.getOpenid();  //用户openid
String key = "";  //商户平台API秘钥
// 加密,这里只列举必填字段
Map<String, String> map = new HashMap<String, String>();
map.put("body", body);//商品描述
map.put("mch_id", MCHID);//商户平台id
map.put("appid", WX_APPID);//公众号id
map.put("nonce_str", nonceStr);//随机字符串
map.put("notify_url", WX_PAY_CALLBACK);//异步回调api
map.put("spbill_create_ip", ip);//支付ip
map.put("out_trade_no", orderSn);//商品订单号
map.put("total_fee", relAmount);//真实金额
map.put("trade_type", "JSAPI");//JSAPI、h5调用
map.put("openid", openid);//支付用户openid
String paySign = "";
try {
    paySign = WeixinUtil.getPayCustomSign(map,key);//WeixinUtil类会在下面展示
    //这一句是签名生成算法
} catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
map.put("sign",paySign);

System.out.println("sign  " + paySign);
String xml = "<xml>" +
                "<appid>"+ WX_APPID +"</appid>"+
                "<body>"+ body +"</body>"+
                "<mch_id>"+ MCHID +"</mch_id>"+
                "<nonce_str>"+ nonceStr +"</nonce_str>"+
                "<notify_url>"+ WX_PAY_CALLBACK +"</notify_url>"+
                "<openid>"+ openid +"</openid>"+
                "<out_trade_no>"+ orderSn +"</out_trade_no>"+
                "<spbill_create_ip>"+ ip +"</spbill_create_ip>"+
                "<total_fee>"+ relAmount + "" +"</total_fee>"+
                "<trade_type>JSAPI</trade_type>"+
                "<sign>"+ paySign +"</sign>"+
             "</xml>";
System.out.println(xml);
//将map转为XML格式
//统一下单,这里不用改 
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; //统一下单接口
String xmlStr = WeixinUtil.post(url, xml); //post请求数据
System.out.println(xmlStr);
String prepayid = ""; 
if (xmlStr.indexOf("SUCCESS") != -1) {  
Map<String, String> map2 = null;  
try {
map2 = WeixinUtil.xmlToMap(xmlStr);  
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}  

prepayid = (String) map2.get("prepay_id");  //获取prepayid
System.out.println("prepay_id  " + prepayid);

现在如果返回了prepay id,那么这一步就算完成,如果出现错误,一般都是签名出错,这时候就要好好看看有没有语法错误,或者签名算法有误。

3.第二次签名,然后传值到前端

long timestamp = System.currentTimeMillis() / 1000; //时间戳
Map<String, String> signMap = new HashMap<String, String>();
signMap.put("appId", WX_APPID);//appid
signMap.put("timeStamp", String.valueOf(timestamp));
signMap.put("nonceStr", nonceStr);
signMap.put("package", "prepay_id="+prepayid);
signMap.put("signType", "MD5");
String paySign2 = "";
try {
paySign2 = WeixinUtil.getPayCustomSign(signMap,key);
System.out.println(paySign2);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("timestamp" + timestamp);
//以下的数据需要传到前端,可以用自己的方法传值
request.setAttribute("appId", WX_APPID); 
request.setAttribute("paytimestamp", String.valueOf(timestamp));
request.setAttribute("paynonceStr", nonceStr);
request.setAttribute("paypackage", "prepay_id="+prepayid);
request.setAttribute("paysignType","MD5");
request.setAttribute("paySign", paySign2);
//去到确认支付页面,返回页面方式不同,(例:pay.html页面)

4.H5调用微信支付接口 官方文档内含代码
只需把参数改为自己的,调用接口就可以完成支付

5.我用到的WeixinUtil类,只是写了用到的一部分,导入的包可能很多没用。。

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import net.sf.json.JSONObject;

public class WeixinUtil {


    private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",  
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };  


    public static String post(String url,String outStr){
        DefaultHttpClient httpClient = new DefaultHttpClient();
        HttpPost httpPost = new HttpPost(url);
        String res =null;
        try {
            httpPost.setEntity(new StringEntity(outStr, "utf-8"));
            HttpResponse response = httpClient.execute(httpPost);
            String result = EntityUtils.toString(response.getEntity(),"utf-8");
            res = result;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return res;
    }


    /**
     * 获取支付所需签名
     */
    public static String getPayCustomSign(Map<String, String> bizObj,String key) throws Exception {
        String bizString = FormatBizQueryParaMap(bizObj, false);
        return sign(bizString, key);
    }

  //支付所需签名处调用此方法
    public static String sign(String content, String key)
            throws Exception{
        String signStr = "";
        signStr = content + "&key=" + key;
        return MD5(signStr).toUpperCase();
    }

  //上一方法,MD5加密处理
    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();
            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];
            }
            return new String(str);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
       }
    }
    /**
     * 字典排序
     */
    public static String FormatBizQueryParaMap(Map<String, String> paraMap,
            boolean urlencode) throws Exception {
        String buff = "";
        try {
            List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(paraMap.entrySet());
            Collections.sort(infoIds,
                    new Comparator<Map.Entry<String, String>>() {
           public int compare(Map.Entry<String, String> o1,
                  Map.Entry<String, String> o2) {
                 return (o1.getKey()).toString().compareTo(
                                    o2.getKey());
                        }
                    });
            for (int i = 0; i < infoIds.size(); i++) {
                Map.Entry<String, String> item = infoIds.get(i);
                //System.out.println(item.getKey());
                if (item.getKey() != "") {
                    String key = item.getKey();
                    String val = item.getValue();
                    if (urlencode) {
                        val = URLEncoder.encode(val, "utf-8");
                    }
                    buff += key + "=" + val + "&";
                }
            }
            if (buff.isEmpty() == false) {
                buff = buff.substring(0, buff.length() - 1);
            }
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }
        return buff;
    }

    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;
        }

    }
}

总结

官方文档写的比较难懂,其他的又比较杂,有的不能用,当然也可以github找demo,这也是我第一次做这些,写的很难看不要在意,如果有错的地方希望提出来,欢迎指导交流。


小人物