1. 程式人生 > >微信公眾號支付詳細過程

微信公眾號支付詳細過程

背景

公司的公眾號有個保險專案需要用到微信支付,官網java文件寫的並不是很詳細。個人根據網上一些demo總結出來的微信支付功能,中間遇到過幾個坑,後續會詳細講解。話不多說,先上程式碼!

一.轉換MD5格式類MD5Util類

import java.security.MessageDigest;

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

二.統一下單引數類UnifiedOrderRequest

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方法
}		

三.統一下單返回引數類UnifiedOrderRespose


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;                //二維碼連結
//....以下省略set和get方法
}	

四.將返回結果轉換為json類WXAuthUtil,該類部分微信引數,其中MCH_ID、APPID、APPSECRET在微信公眾號設定,KEY在微信商戶號上設定,KEY用於後面生成簽名。

import java.io.IOException;
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.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class WXAuthUtil {
    public static final String MCH_ID="";//商戶號
    public static final String APPID="";//
    public static final String APPSECRET ="";
    public static final String KEY="";
    public static JSONObject doGetJson(String url) throws ClientProtocolException, IOException {
        JSONObject jsonObject =null;
        DefaultHttpClient client = new DefaultHttpClient();
        HttpGet httpGet =new HttpGet(url);
        HttpResponse response =  client.execute(httpGet);
        HttpEntity entity =response.getEntity();
        if(entity!=null)
        {
            //把返回的結果轉換為JSON物件
            String result =EntityUtils.toString(entity, "UTF-8");
            jsonObject =JSON.parseObject(result);
        }
        
        return jsonObject;
    }
}

五.簽名方式類WXPayConstants,包含微信返回結果定義的引數

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

六.請求連線類HttpKit,統一下單、查詢訂單用到

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.Response;

/** 
 * https 請求 微信為https的請求 
 * 
 * @author andy 
 * @date 2015-10-9 下午2:40:19 
 */   
public class HttpKit {  
    private static final String DEFAULT_CHARSET = "UTF-8";  
    /** 
     * @return 返回型別: 
     * @throws IOException 
     * @throws UnsupportedEncodingException 
     * @throws NoSuchProviderException 
     * @throws NoSuchAlgorithmException 
     * @throws KeyManagementException 
     * @description 功能描述: get 請求 
     */  
    public static String get(String url, Map<String, String> params, Map<String, String> headers) throws IOException, ExecutionException, InterruptedException {  
        AsyncHttpClient http = new AsyncHttpClient();  
        AsyncHttpClient.BoundRequestBuilder builder = http.prepareGet(url);  
        builder.setBodyEncoding(DEFAULT_CHARSET);  
        if (params != null && !params.isEmpty()) {  
            Set<String> keys = params.keySet();  
            for (String key : keys) {  
                //builder.addQueryParame(key, params.get(key));
                builder.addQueryParam(key, params.get(key));
            }  
        }  
  
        if (headers != null && !headers.isEmpty()) {  
            Set<String> keys = headers.keySet();  
            for (String key : keys) {  
                builder.addHeader(key, params.get(key));  
            }  
        }  
        Future<Response> f = builder.execute();  
        String body = f.get().getResponseBody(DEFAULT_CHARSET);  
        http.close();  
        return body;  
    }  
  
    /** 
     * @return 返回型別: 
     * @throws IOException 
     * @throws UnsupportedEncodingException 
     * @throws NoSuchProviderException 
     * @throws NoSuchAlgorithmException 
     * @throws KeyManagementException 
     * @description 功能描述: get 請求 
     */  
    public static String get(String url) throws KeyManagementException, NoSuchAlgorithmException, NoSuchProviderException, UnsupportedEncodingException, IOException, ExecutionException, InterruptedException {  
        return get(url, null);  
    }  
  
    /** 
     * @return 返回型別: 
     * @throws IOException 
     * @throws NoSuchProviderException 
     * @throws NoSuchAlgorithmException 
     * @throws KeyManagementException 
     * @throws UnsupportedEncodingException 
     * @description 功能描述: get 請求 
     */  
    public static String get(String url, Map<String, String> params) throws KeyManagementException, NoSuchAlgorithmException, NoSuchProviderException, UnsupportedEncodingException, IOException, ExecutionException, InterruptedException {  
        return get(url, params, null);  
    }  
  
    /** 
     * @return 返回型別: 
     * @throws IOException 
     * @throws NoSuchProviderException 
     * @throws NoSuchAlgorithmException 
     * @throws KeyManagementException 
     * @description 功能描述: POST 請求 
     */  
    public static String post(String url, Map<String, String> params) throws IOException, ExecutionException, InterruptedException {  
        AsyncHttpClient http = new AsyncHttpClient();  
        AsyncHttpClient.BoundRequestBuilder builder = http.preparePost(url);  
        builder.setBodyEncoding(DEFAULT_CHARSET);  
        if (params != null && !params.isEmpty()) {  
            Set<String> keys = params.keySet();  
            for (String key : keys) {  
                //builder.addParameter(key, params.get(key));
                builder.addQueryParam(key, params.get(key));
            }  
        }  
        Future<Response> f = builder.execute();  
        String body = f.get().getResponseBody(DEFAULT_CHARSET);  
        http.close();  
        return body;  
    }  
  
    public static String post(String url, String s) throws IOException, ExecutionException, InterruptedException {  
        AsyncHttpClient http = new AsyncHttpClient();  
        AsyncHttpClient.BoundRequestBuilder builder = http.preparePost(url);  
        builder.setBodyEncoding(DEFAULT_CHARSET);  
        builder.setBody(s);  
        Future<Response> f = builder.execute();  
        String body = f.get().getResponseBody(DEFAULT_CHARSET);  
        http.close();  
        return body;  
    }  
      
}  

七.支付工具類

import java.io.BufferedOutputStream;  
import java.io.BufferedReader;  
import java.io.ByteArrayInputStream;  
import java.io.InputStream;  
import java.io.InputStreamReader;
import java.io.StringReader;
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 org.xml.sax.InputSource;

import com.rybsj.entity.PaymentOrder;
import com.rybsj.tools.WXPayConstants.SignType;
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 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.jdom.Document;  
import org.jdom.Element;  
import org.jdom.input.SAXBuilder;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;

/** 
 * 支付工具類 
 * @author lh 
 */
public class WXPayUtil {
	public 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("nonce_str"));//隨機字串      
        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)!=null&&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;    
    }    
    /**
     * map轉物件
     * @param map
     * @return
     */
    public static PaymentOrder getPaymentOrders(Map<String,String> map) {
    	PaymentOrder payOrder=new PaymentOrder();
    	payOrder.setAppid(map.get("appid"));
    	payOrder.setOpenid(map.get("openid"));
    	payOrder.setOrdetaileid(Integer.parseInt(map.get("out_trade_no")));
    	payOrder.setTotal_fee(Integer.parseInt(map.get("total_fee")));
    	payOrder.setTrade_type(map.get("trade_type"));
    	//payOrder.setTime_start(map.get("time_start"));
    	//payOrder.setTime_expire(map.get("time_expire"));
    	return payOrder;
    }
    /**
     * 物件轉map
     */
    public static Map<String,String> getMaptoOrder(PaymentOrder porder){
    	Map<String,String> map=new HashMap<String,String>();
    	map.put("appId", porder.getAppid());
    	map.put("timeStamp", porder.getTimestamp());
    	map.put("nonceStr", porder.getNonce_str());
    	map.put("signType", porder.getSignType());
    	map.put("package", porder.getPackages());
    	map.put("paySign", porder.getPaySign());
    	return map;
    }
    /**
     * description: 解析微信通知xml
     * 
     * @param xml
     * @return
     * @author ex_yangxiaoyi
     * @see
     */
    public static Map parseXmlToList(String xml) {
        Map retMap = new HashMap();
        try {
            StringReader read = new StringReader(xml);
            // 建立新的輸入源SAX 解析器將使用 InputSource 物件來確定如何讀取 XML 輸入
            InputSource source = new InputSource(read);
            // 建立一個新的SAXBuilder
            SAXBuilder sb = new SAXBuilder();//
            // 通過輸入源構造一個Document
            Document doc = (Document) sb.build(source);
            Element root = doc.getRootElement();// 指向根節點
            List<Element> es = root.getChildren();
            if (es != null && es.size() != 0) {
                for (Element element : es) {
                    retMap.put(element.getName(), element.getValue());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return retMap;
    }
}

以上為工具類,後面講支付流程。首先需要使用者授權獲取openid,注意不要認為官方文件必填項顯示此引數為否就不用openid了,後面解釋有說明當trade_type=JSAPI(即公眾號支付)時此引數必填。

首先獲取使用者許可權,需要設定設定回撥域名(即redirect_uri的域名),該域名需為80埠,且需要下載一個txt檔案到該域名下,具體操作如下圖:


此外獲取access_token還要配置IP白名單,如下圖:找到開發下面的基本配置,點選IP白名單的配置,多個ip可用回車鍵隔開



配置完後就可以通過微信授權獲取openid了。看了很多demo微信授權連結都是直接寫在後臺的,本人試了下寫在前臺也能用。其中appid改成自己公眾號的appid,redirect_uri就是剛才設定的授權回撥域名,getWXUserInformation是對應後臺的獲取openid的方法,此方法也可獲取微信頭像和名字等資訊。state=123是授權時帶過去的引數,可以隨便修改。scope=snsapi_userinfo時表示授權時會彈出授權頁面需要使用者同意,此時可以獲取使用者其他資訊,當scope=snsapi_base時表示授權不會彈出授權頁面,此時只能獲取openid。

window.location="https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx。。。。。&redirect_uri=。。。/getWXUserInformation&response_type=code&scope=snsapi_userinfo&state=123#wechat_redirect";

下面附上後臺獲取openid業務層類Access_tokenAction

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.apache.http.client.ClientProtocolException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import com.alibaba.fastjson.JSONObject;
import com.rybsj.service.UserService;
import com.rybsj.tools.WXAuthUtil;
@Controller
public class Access_tokenAction {
	@Resource(name="userService")
	  private UserService userimpl;
	private static Logger logger=LoggerFactory.getLogger(Access_tokenAction.class);
	 /**
	   * 獲取微信使用者資訊
	 * @throws IOException 
	 * @throws ClientProtocolException 
	   */
	  @RequestMapping("/getWXUserInformation")
	  public String getWXUserInformation(HttpServletRequest request) throws ClientProtocolException, IOException {
		  /*
	       * start 獲取微信使用者基本資訊
	       */
		  String code=request.getParameter("code");
		  System.out.println(code);
		  logger.info("code="+code);
		//第二步:通過code換取網頁授權access_token
	      String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+WXAuthUtil.APPID
	             + "&secret="+WXAuthUtil.APPSECRET
	             + "&code="+code
	             + "&grant_type=authorization_code";

	     System.out.println("url:"+url);
	     logger.info("url="+url);
	     JSONObject jsonObject = WXAuthUtil.doGetJson(url);
	     System.out.println(jsonObject.toString());
	     logger.info("獲取token返回資訊為:"+jsonObject.toString());
	     /*
	      { "access_token":"ACCESS_TOKEN",
	         "expires_in":7200,
	         "refresh_token":"REFRESH_TOKEN",
	         "openid":"OPENID",
	         "scope":"SCOPE" 
	        }
	      */
	     String openid = jsonObject.getString("openid");
	     String access_token = jsonObject.getString("access_token");
	     String refresh_token = jsonObject.getString("refresh_token");
	     //第五步驗證access_token是否失效;暫時不需要
	     String chickUrl="https://api.weixin.qq.com/sns/auth?access_token="+access_token+"&openid="+openid;

	     JSONObject chickuserInfo = WXAuthUtil.doGetJson(chickUrl);
	     System.out.println(chickuserInfo.toString());
	     logger.info("返回驗證資訊:"+chickuserInfo.toString());
	     if(!"0".equals(chickuserInfo.getString("errcode"))){
	         // 第三步:重新整理access_token(如果需要)-----暫時沒有使用,參考文件https://mp.weixin.qq.com/wiki,
	         String refreshTokenUrl="https://api.weixin.qq.com/sns/oauth2/refresh_token?appid="+openid+"&grant_type=refresh_token&refresh_token="+refresh_token;

	         JSONObject refreshInfo = WXAuthUtil.doGetJson(chickUrl);
	         /*
	          * { "access_token":"ACCESS_TOKEN",
	             "expires_in":7200,
	             "refresh_token":"REFRESH_TOKEN",
	             "openid":"OPENID",
	             "scope":"SCOPE" }
	          */
	         System.out.println(refreshInfo.toString());
	         access_token=refreshInfo.getString("access_token");
	     }
	    
	    // 第四步:拉取使用者資訊(需scope為 snsapi_userinfo)
	    String infoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token="+access_token
	             + "&openid="+openid
	             + "&lang=zh_CN";
	     System.out.println("infoUrl:"+infoUrl);
	     JSONObject userInfo = WXAuthUtil.doGetJson(infoUrl);
	     logger.info("使用者資訊為:"+userInfo.toString());
	     /*
	      {    "openid":" OPENID",
	         " nickname": NICKNAME,
	         "sex":"1",
	         "province":"PROVINCE"
	         "city":"CITY",
	         "country":"COUNTRY",
	         "headimgurl":    "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
	         "privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],
	         "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
	         }
	      */
	     System.out.println("JSON-----"+userInfo.toString());
	     System.out.println("名字-----"+userInfo.getString("nickname"));
	     System.out.println("頭像-----"+userInfo.getString("headimgurl"));
	     //request.getSession().setAttribute("openid", openid);
	     Map<String, String> map=new HashMap<String,String>();
	     map.put("openid", openid);
	     map.put("nickname", userInfo.getString("nickname"));
	     map.put("headimgurl", userInfo.getString("headimgurl"));
	     request.getSession().setAttribute("wxUserMap", map);
	     /*
	      * end 獲取微信使用者基本資訊
	      */
	     //獲取到使用者資訊後就可以進行重定向,走自己的業務邏輯了。。。。。。
	     //接來的邏輯就是你係統邏輯了,請自由發揮 
		  return "operatingVan";
	  }
}

獲取openid後就可以微信下單了。首先設定支付授權目錄,該目錄以前是在微信平臺設定的 現在改在商戶平臺設定了


先附上前端程式碼orderPayment.jsp和後臺對應下單方法getPaymentOrder,此頁面首先通過pay()方法從後臺的獲取getPaymentOrder方法獲取微信支付的引數appId,timeStamp,nonceStr,signType,package,paySign.然後呼叫onBridgeReady()方法實現微信支付,當用戶輸入密碼後點擊立即支付會跳轉到商戶後臺的getPaymentResult()方法內,此處跳轉可在getPaymentOrder()方法的下單引數notify_url中設定

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE html>
<html lang="zh-cn">
  <head>
  <base href="<%=basePath%>">
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>微信支付</title>
    <script>
	    setRem();
	    window.addEventListener('orientationchange', setRem);
	    window.addEventListener('resize', setRem);
	    function setRem() {
	        var html = document.querySelector('html');
	        var width = html.getBoundingClientRect().width;
	        html.style.fontSize = width / 16 + 'px';
	    }
    </script>
     <link rel="stylesheet" href="css/common.css" />
     <link rel="stylesheet" type="text/css" href="css/payment.css" />
  </head>
  <body>
  	<!--訂單需支付-->
    <div class="payment-box">
    	<p>訂單需支付</p>
  		<ul>
  			<li>
  				<span class="left">首期款</span>
  				<span class="right">¥<span class="decime" >${order.initialcost}</span></span>
  			</li>
				<li>
  				<span class="left">服務費</span>
  				<span class="right">¥<span class="decime" >${order.servicecost}</span></span>
  			</li>
  			<li>
  				<span class="left">合計</span>
  				<span class="right amount">¥<span class="decime">${sum }</span></span>
  			</li>
  		</ul>
    </div>
    <div class="height20"></div>
    <!--是否需要開發票-->
    <div class="invoice">
    	<div class="left">是否需要開發票</div>
    	<div class="right"><img src="images/Off.png" id="onOff" class="active"></div>
    </div>
    <div class="address">
    	<ul>
    		<li>
    			<div class="info">
    				發票資訊
    			</div>
    			<div class="info-input">
    				<input type="text" placeholder="請輸入">
    				<i><img src="images/icon-right.png"></i>
    			</div>
    		</li>
    		<li>
    			<div class="second-info">
    				<span>收貨地址</span>
    				<input type="text" name="" placeholder="我是一個收件地址,也可能是其它"/>
    				<i><img src="images/icon-right.png"></i>
    			</div>
    		</li>
    	</ul>
    </div>
    <div class="height20"></div>
    <!--支付方式-->
    <div class="payment-style">
    	 <ul>
    	 	<li class="payments">
    	 		<div class="left">
    	 			<img src="images/select-false.png" class="weixin-img"><span>微信支付</span>
    	 		</div>
    	 		<div class="right">
    	 			<img src="images/icon-right.png">
    	 		</div>
    	 	</li>
    	 	<li class="payments">
    	 		<div class="left">
    	 			<img src="images/select-false.png" class="transfer-img"><span>轉賬支付</span>
    	 		</div>
    	 		<div class="right">
    	 			<img src="images/icon-down.png" class="iconDow">
    	 		</div>
    	 	</li>
    	 	<div class="child-info">
    	 		<ul>
    	 			<li>
    	 				  <div class="left">賬戶名</div>
    	 				  <div class="right">aaa</div>  	 				
    	 			</li>
    	 			<li>
    	 				  <div class="left">開戶行</div>
    	 				  <div class="right">中國銀行</div>  	 				
    	 			</li>
    	 			<li>
    	 				  <div class="left">賬號</div>
    	 				  <div class="right">0000 1111 222 3333 4444</div>  	 				
    	 			</li>
    	 		</ul>
    	 	</div>
    	 	<li class="payments">
    	 		<div class="left">
    	 			<img src="images/select-false.png" class="qr-img"><span>二維碼支付</span>
    	 		</div>
    	 		<div class="right">
    	 			<img src="images/icon-down.png" class="iconDow">
    	 		</div>
    	 	</li>
    	 	<div class="last-info">
    	 		<img src="images/ma.png"> 
    	 	</div>
    	 </ul>
    </div>
	<input type="hidden" id="order" value="${order.ordetaileid }" />
    <script type="text/javascript" src="js/jquery.js" ></script>
    <script type="text/javascript" src="js/carinformation.js" ></script>
    <script>
    var appId,timeStamp,nonceStr,pg,signType,paySign;
    var ordetaileid=$("#order").val();
    var initialcost=parseFloat($(".decime").eq(0).html());
    var servicecost=parseFloat($(".decime").eq(1).html());
    var sum=parseFloat($(".decime").eq(2).html());
    $(".decime").eq(0).html(formatCurrency(initialcost));
    $(".decime").eq(1).html(formatCurrency(servicecost));
    $(".decime").eq(2).html(formatCurrency(sum));
    	(function(){
    		//開關事件
    		$("#onOff").on("touchend",function(){
	    			if($(this).hasClass("active")){
	    				$(this).attr("src","images/On.png");
	    				$(this).removeClass("active");
	    			}else{
	    				$(this).attr("src","images/Off.png");
	    				$(this).addClass("active");
	    			}
	    			$(".address").stop().slideToggle();
    		})
    		
    		//支付事件
    		$(".payments").on("touchend",function(){
    			 var judgeClass = $(this).find(".left img").attr("class");
    			$(this).find(".left img").attr("src","images/select-true.png").end().siblings("li").find(".left img").attr("src","images/select-false.png");
    			 if(judgeClass == "weixin-img"){
    			 	$(".last-info").stop().slideUp();
    			 	$(".child-info").stop().slideUp();
    			 	$(this).find(".right img").attr("src","images/icon-right.png");
    			 	//alert("微信支付");
    			 	//微信支付
    			 	 	pay();    			 	
    			 }else if(judgeClass == "transfer-img"){//轉賬支付
    			 	if($(".child-info").is(":visible")){
    			 		$(".child-info").stop().slideUp();
    			 		$(this).find(".left img").attr("src","images/select-false.png");
    			 		$(this).find(".right .iconDow").attr("src","images/icon-down.png");
    			 	}else{
    			 		$(".child-info").stop().slideDown();
    			 		$(this).find(".right .iconDow").attr("src","images/icon-up.png").end().siblings("li").find(".right .iconDow").attr("src","images/icon-down.png");
    			 	}
    			 	  $(".last-info").stop().slideUp();
    			 }else if(judgeClass == "qr-img"){//二維碼支付
    			 	if($(".last-info").is(":visible")){
    			 		$(".last-info").stop().slideUp();
    			 		$(this).find(".left img").attr("src","images/select-false.png");
    			 		$(this).find(".right .iconDow").attr("src","images/icon-down.png");
    			 	}else{
    			 		$(".last-info").stop().slideDown();
    			 		$(this).find(".right .iconDow").attr("src","images/icon-up.png").end().siblings("li").find(".right .iconDow").attr("src","images/icon-down.png");
    			 	}
    			 	  $(".child-info").stop().slideUp();
    			 }
    		})
    		
    	})();
    	
    	function pay() {
    		$.ajax({
    	      	url:"getPaymentOrder",
    	      	dataType:"json",
    	      	async:false,
    	      	data:"ordetaileid="+ordetaileid+"&money="+sum,
    	      	success:function(data){
    	      		if(data==1){
    	      			alert("該訂單已支付");//呼叫支付查詢介面
    	      			$.ajax({
    	        	      	url:"findWXOrder",
    	        	      	dataType:"json",
    	        	      	async:false,
    	        	      	data:"ordetaileid="+ordetaileid,
    	        	      	success:function(v){
    	        	      		if(v==2){
    	        	      			window.location="userSignList.html";//支付成功,跳轉至簽約介面
    	        	      			return;
    	        	      		}else{
    	        	      			return;
    	        	      		}
    	        	      		}
    	        	      	})
    	      			return;
    	      		}
    	      		appId=data.appId;
    	      		timeStamp=data.timeStamp;
    	      		nonceStr=data.nonceStr;
    	      		pg=data.package;
    	      		signType=data.signType;
    	      		paySign=data.paySign;
    	      	}
    	      })
    	      if (typeof WeixinJSBridge == "undefined"){//非微信瀏覽器
    	      	   if( document.addEventListener ){
    	      	       document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
    	      	   }else if (document.attachEvent){
    	      	       document.attachEvent('WeixinJSBridgeReady', onBridgeReady); 
    	      	       document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
    	      	   }
    	      	}else{//微信內部瀏覽器
    	      	   onBridgeReady();
    	      	}
    	  	  window.event.returnValue = false;
    	        return false;
		}
    	
    	 function onBridgeReady(){
    	  	   WeixinJSBridge.invoke(
    	  	       'getBrandWCPayRequest', {
    	  	           "appId":appId,     //公眾號名稱,由商戶傳入     
    	  	           "timeStamp":timeStamp,         //時間戳,自1970年以來的秒數     
    	  	           "nonceStr":nonceStr, //隨機串     
    	  	           "package":pg,  
    	  	           "signType":signType,         //微信簽名方式:     
    	  	           "paySign":paySign //微信簽名 
    	  	       },
    	  	       function(res){ 
    	  	    	   //支付驗證簽名失敗!
    	  	           if(res.err_msg == "get_brand_wcpay_request:ok" ) {
    	  	        	   alert("支付成功");
    	  	        	   window.location="userSignList.html";
    	  	        	   }// 使用以上方式判斷前端返回,微信團隊鄭重提示:res.err_msg將在使用者支付成功後返回    ok,但並不保證它絕對可靠。 
    	  	       }
    	  	   ); 
    	  	}
    </script>
  </body>
</html>

後臺業務類PaymentOrderAction,此處注意兩次簽名(下單和獲取prepay_id後再次簽名)都要用MD5加密,簽名用到的key是商戶後臺的金鑰,本人之前用的是APPSECRET生成訂單一直失敗。getPaymentResult()此方法獲取支付結果並可操作自己的業務邏輯,然後將返回結果傳到前臺。若支付成功,前臺res.err_msg == "get_brand_wcpay_request:ok"。同時也可呼叫查詢訂單介面路徑findWXOrder和關閉訂單介面路徑wxCloseorder

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.alibaba.fastjson.JSONObject;
import com.rybsj.entity.PaymentOrder;
import com.rybsj.service.PaymentOrderService;
import com.rybsj.tools.HttpKit;
import com.rybsj.tools.WXAuthUtil;
import com.rybsj.tools.WXPayUtil;

@Controller
public class PaymentOrderAction extends BaseAction{
@Resource(name="paymentOrderService")
private PaymentOrderService paymentImpl;
/**
 * 點選微信支付彈出支付頁面並獲取支付引數
 * @param request
 * @param money
 * @return
 * @throws Exception 
 */
@RequestMapping("/getPaymentOrder")
@ResponseBody
public String getPaymentOrder(HttpServletRequest request,int ordetaileid,String sum) throws Exception {
	Map<String,String> wxUserMap = (Map<String,String>)request.getSession().getAttribute("wxUserMap");
	String openId=wxUserMap.get("openid");
	PaymentOrder order=paymentImpl.getPaymentOrderOrdetaileid(ordetaileid);
	Map<String, String> payMap=null;
	//String openId="oW-bM0tabXXrrNzfjeq4jNlahGJQ";
	//double sum2=mul(Double.parseDouble(sum),100);//金額轉分
	//System.out.println(sum2);
	if(order==null||order.getStatus()==3) {//該訂單不存在或失效
	Map<String, String> paraMap = new HashMap<String, String>();
	paraMap.put("appid", WXAuthUtil.APPID);
	paraMap.put("body", "測試購買支付");
	paraMap.put("mch_id", WXAuthUtil.MCH_ID);//商戶id
	paraMap.put("nonce_str", WXPayUtil.generateNonceStr());
	paraMap.put("notify_url", "---------/getPaymentResult");// 此路徑是微信伺服器呼叫支付結果通知路徑
	paraMap.put("openid", openId);
	paraMap.put("out_trade_no", ordetaileid+"");
	//paraMap.put("spbill_create_ip", "123.12.12.123"); 
	paraMap.put("spbill_create_ip", "192.168.1.123"); 
    paraMap.put("total_fee", "1"); //金額 
    paraMap.put("trade_type", "JSAPI"); //交易型別(公眾號支付)
    System.out.println(paraMap.toString());
    String sign =WXPayUtil.generateSignature(paraMap, WXAuthUtil.KEY);
    paraMap.put("sign", sign);
    // 統一下單 https://api.mch.weixin.qq.com/pay/unifiedorder  
    String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    String xml = WXPayUtil.GetMapToXML(paraMap);
    String xmlStr = HttpKit.post(url, xml);
    System.out.println("xml="+xmlStr);
    // 微信預付訂單id  
    String prepay_id = "";  
    Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
    if ("SUCCESS".equals(map.get("return_code"))&&"SUCCESS".equals(map.get("result_code"))) { 
        //Map<String, String> map = doXMLParse(xmlStr);  	
        prepay_id = map.get("prepay_id");  
    } else {
    	System.out.println("呼叫微信支付出錯,返回狀態碼:"+map.get("return_code")+",返回資訊:"+map.get("return_msg"));
    	return JSONObject.toJSONString(2);//呼叫微信支付出錯
    } 
    //保險系統訂單入庫
    PaymentOrder payOrder=WXPayUtil.getPaymentOrders(paraMap);
    payOrder.setPrepay_id(prepay_id);
    payOrder.setTime_start(getTimesMiao());
    payOrder.setStatus(1);
    if(order==null)paymentImpl.addPaymentOrder(payOrder);
    else if(order.getStatus()==3) paymentImpl.updatePaymentOrder(payOrder);
    //int pid=payOrder.getPid();//商戶預付訂單id
    //
    String timeStamp = WXPayUtil.getCurrentTimestamp()+"";//獲取當前時間戳(時分秒)
    String nonceStr=WXPayUtil.generateNonceStr();
    payMap = new HashMap<String, String>();  
    payMap.put("appId", paraMap.get("appid"));  
    payMap.put("timeStamp", timeStamp);  
    payMap.put("nonceStr", nonceStr);  
    payMap.put("signType", "MD5");  
    payMap.put("package", "prepay_id=" + prepay_id);
    String paySign = WXPayUtil.generateSignature(payMap, WXAuthUtil.KEY); 
    System.out.println(paySign);
    payMap.put("paySign", paySign);
    //新增商戶系統訂單傳到前臺的資料appid,timestamp,noncestr,signType,package
    payOrder.setNonce_str(nonceStr);
    payOrder.setTimestamp(timeStamp);
    payOrder.setSignType("MD5");
    payOrder.setPackages(payMap.get("package"));
    payOrder.setPaySign(paySign);
    paymentImpl.updatePaymentOrder(payOrder);
	}else if(order.getStatus()==1){//訂單未支付,
		payMap = WXPayUtil.getMaptoOrder(order);	
	}else {
		//該訂單已支付,但由於各種原因未跳轉至指定頁面
		return JSONObject.toJSONString(1);//訂單已支付
	}
	String str=JSONObject.toJSONString(payMap);
    System.out.println(str);
	return str;
}

/**
 * 支付結果通知頁面
 * @throws Exception 
 */
@RequestMapping("/getPaymentResult")
@ResponseBody
public String getPaymentResult(HttpServletRequest request,HttpServletResponse response) throws Exception {
	String out_trade_no=null;
    String return_code =null;
    String result_code=null;
    Map<String, Object> resultMap=null;
    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();
        String resultStr  = new String(outSteam.toByteArray(),"utf-8");
        System.out.println(resultStr);
        //logger.info("支付成功的回撥:"+resultStr);
        resultMap = WXPayUtil.parseXmlToList(resultStr);
        request.setAttribute("out_trade_no", out_trade_no);
        //通知微信.非同步確認成功.必寫.不然微信會一直通知後臺.八次之後就認為交易失敗了.
        //response.getWriter().write(RequestHandler.setXML("SUCCESS", ""));
    }  catch (Exception e) {
        //logger.error("微信回撥接口出現錯誤:",e);
        try {
            //response.getWriter().write(RequestHandler.setXML("FAIL", "error"));
        } catch (Exception e1) {
            e1.printStackTrace();
        }
    } 
    PaymentOrder paymentOrder=new PaymentOrder();
    Map<String,String> return_data = new HashMap<String,String>();
    return_code=(String)resultMap.get("return_code");
    out_trade_no=(String)resultMap.get("out_trade_no");
    if(return_code.equals("SUCCESS")){
        //支付成功的業務邏輯,如果支付成功,則修改商戶訂單的狀態,
    	paymentOrder=paymentImpl.getPaymentOrderOrdetaileid(Integer.parseInt(out_trade_no));
    	if(paymentOrder==null) {
    		return_data.put("return_code", "FAIL");  
            return_data.put("return_msg", "訂單不存在");
            return WXPayUtil.mapToXml(return_data);
    	}else {
    		result_code=(String)resultMap.get("result_code");
    		if(result_code.equals("SUCCESS")) {
    			if(paymentOrder.getStatus()==2) {
    				 return_data.put("return_code", "SUCCESS");  
                     return_data.put("return_msg", "OK");
                     return WXPayUtil.mapToXml(return_data);
    			}else {
    				//String sign = resultMap.get(