微信公衆號H5支付(SSM框架)

 首先感謝一下【架構之路】盆友!我就是在他的基礎之上改的。原文連接。再三感謝!php

在最下,我會貼上全部的代碼。我代碼log日誌打的很是的多,可能會顯得很羅嗦。見諒。html

前面的一些純文字介紹看的會很累。可是建議好好看一下。尤爲是我貼上的微信的官方文檔。裏面其實已經寫的很是清楚了。前端

前言

  • 微信公衆號內支付在頁面端較爲簡單。只須要調用微信內置的js方法,將微信須要的參數傳遞過去。
  • 與其餘支付方式不一樣的是,公衆號支付須要的參數有一項名爲open_id。這個參數須要客戶端請求固定鏈接、用戶手動贊成後才能夠獲取到。

開發

  開發前的準備

    • 申請微信商戶

        略java

    • 微信相關設置

        IP白名單:首頁->基本配置->IP白名單。寫入服務器的地址。
git

        進入微信商戶中心,下載證書,安裝控件、證書,設置API密鑰(KEY)。web

        設置支付受權目錄。http://www.*.com/cms/wepay/。ajax

    • 獲取微信商戶信息

        MCH_ID:首頁->微信支付。能夠查看到微信支付的商戶號。
spring

        APPID、APP_SECRET:首頁->基本配置;能夠找到開發者ID。同時重置APP_SECRET。數據庫

    • 注意1:咱們已得到了4個微信支付的常量。包括APPID、APP_SECRET、MCH_ID、KEY;咱們再加一個支付類型的常量:TRADE_TYPE=JSAPI。
    • 注意2:受權目錄要求:1必須爲經過ICP備案的地址。2訪問項目端口必須爲80或334。像個人項目最先部署在8080端口,就一直在報錯。其中cms爲項目名;wepay爲controller的mapping值。下面是微信的要求。

        全部使用公衆號支付方式發起支付請求的連接地址,都必須在支付受權目錄之下;apache

        最多設置5個支付受權目錄,且域名必須經過ICP備案;

        頭部要包含http或https,須細化到二級或三級目錄,以左斜槓「/」結尾。

  正式開發

    步驟1:【微信網頁受權】獲取用戶open_id 。

     官方文檔    官方測試地址(在線調試工具)

     簡單說。設置好受權方式,來獲取code。由code獲取accessToken,由accessToken再獲取用戶的open_id和其餘信息。

      1 第一步:用戶贊成受權,獲取code

      2 第二步:經過code換取網頁受權access_token

      3 第三步:刷新access_token(若是須要)。得到open_id

      4 第四步:拉取用戶信息(需scope爲 snsapi_userinfo)

      5 附:檢驗受權憑證(access_token)是否有效

    由微信的tab直接請求toPay方法。這個過程,首先是發起受權請求。用戶容許後,開始獲取openid。以後,再跳轉至購買頁面。

首先強調第一點:token是有區別的。
經過code換取的access_token(第二步)和以後拉取用戶時的access_token(第四步)不同。這點尤爲須要注意。第二步的token是oauth2加密的token,而第四步的token只是普通的token。具體請看官方文檔。
其次:token的使用。
下面代碼中的使用方式爲:每次使用時,都去微信api申請一個token。可是微信api每日調用token接口的次數是有限的(一天1萬次)。因此須要更精確的對其進行控制。
能夠將token存入數據庫。使用定時任務刷新token。
我採用了比較簡單的方式,將信息存到了session裏面。取出時,判斷下時間便可。

    步驟2 :【統一下單】準備工做完成。開始正式支付

     後臺文檔頁面文檔

    本項目操做過程爲:用戶手動點擊支付按鈕->獲取支付須要的參數->將預付款訂單參數傳遞給微信。

 1 function onBridgeReady(){
 2         WeixinJSBridge.invoke(
 3             'getBrandWCPayRequest', {
 4                  "appId":appId,     //公衆號名稱,由商戶傳入
 5                  "paySign":sign,         //微信簽名
 6                  "timeStamp":timeStamp, //時間戳,自1970年以來的秒數
 7                  "nonceStr":nonceStr , //隨機串
 8                  "package":packageStr,  //預支付交易會話標識
 9                  "signType":signType     //微信簽名方式
10              },
11              function(res){
12           alert(JSON.stringify(res));//出錯能夠在這裏看看.....
13                  if(res.err_msg == "get_brand_wcpay_request:ok" ) {
14                 //window.location.replace("index.html");
15                   alert('支付成功');
16               }else if(res.err_msg == "get_brand_wcpay_request:cancel"){
17                alert('支付取消');
18                }else if(res.err_msg == "get_brand_wcpay_request:fail" ){
19                 alert('支付失敗');
20               }
21                  //使用以上方式判斷前端返回,微信團隊鄭重提示:res.err_msg將在用戶支付成功後返回    ok,但並不保證它絕對可靠。
22              }
23         );
24     }

    

    步驟3:【異步結果通知】微信支付異步回調通知

    後臺文檔

    • 該連接是經過【統一下單API】中提交的參數notify_url設置,若是連接沒法訪問,商戶將沒法接收到微信通知。
    • 通知url必須爲直接可訪問的url,不能攜帶參數。示例:notify_url:「https://pay.weixin.qq.com/wxpay/pay.action」

    若是出現簽名錯誤相關錯誤,我是用的此頁面的校驗工具進行測試的。微信在線校驗工具

dto實體類。

用戶信息類。若是隻作支付不保存用戶信息,此類可無。

public class UserInfo {
    
    private String openid;

    private String unionid;

    private String headimgurl;

    private String nickname;

    private String refreshtoken;

    private int siteid;

}

微信常量信息類。

public class WeChatConst {

    // 公衆號支付APPID
    public static final String APPID = WePropertieUtil.getValue("APPID");
    
    // 公衆號支付AppSecret
    public static final String APP_SECRET = WePropertieUtil.getValue("APP_SECRET");
    
    // 公衆號支付商戶號
    public static final String MCH_ID = WePropertieUtil.getValue("MCH_ID");
    
    // 商戶後臺配置的一個32位的key,位置:微信商戶平臺-帳戶中心-API安全
    public static final String KEY = WePropertieUtil.getValue("KEY");
    
    // 交易類型
    public static final String TRADE_TYPE = WePropertieUtil.getValue("TRADE_TYPE");
    
    // 根路徑
    public static final String BASE_URL = WePropertieUtil.getValue("BASE_URL");
}

用戶信息類。若是隻作支付不保存用戶信息,此類可無。

public class WeixinLoginUser implements Serializable {

    private static final long serialVersionUID = -8449856597137213512L;

    private String openID;

    private String unionID;

    private String headImageUrl;

    private String nickName;

    private String refreshToken;

    private int siteID;
}

微信支付參數。事實上我只用了裏面極少一部分。

public class WxPaySendData {

    /**
     *  公衆帳號ID
     */
    private String appid;
    
    /**
     *  附加數據
     */
    private String attach;
    
    /**
     *  商品描述
     */
    private String body;
    /**
     *  商戶號
     */
    private String mch_id;
    
    /**
     * 隨機字符串
     */
    private String nonce_str;
    
    /**
     * 通知地址
     */
    private String notiry_url;
    
    /**
     * 商戶訂單號
     */
    private String out_trade_no;
    
    /**
     * 標價金額
     */
    private String total_fee;
    
    /**
     * 交易類型
     */
    private String trade_type;
    
    /**
     * 終端IP
     */
    private String spbill_create_ip;
    
    /**
     * 用戶標識
     */
    private String openid;

    /**
     *  簽名
     */
    private String sign;

    /**
     *  預支付id
     */
    private String prepay_id;
    
    /**
     * 簽名類型:MD5
     */
    private String signType;
    
    /**
     * 時間戳
     */
    private String timeStamp;
    
    /**
     * 微信支付時用到的prepay_id
     */
    private String packageStr;

    private String return_code;
    private String return_msg;
    private String result_code;

    private String bank_type;
    private Integer cash_fee;
    private String fee_type;
    private String is_subscribe;
    private String time_end;

    /**
     *  微信支付訂單號
     */
    private String transaction_id;
}

所用的工具類

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;

/**
 * @Description http請求工具類
 */
public class HttpUtil {

    /**
     * 向指定URL發送GET方法的請求
     * 
     * @param url
     *            發送請求的URL
     * @param param
     *            請求Map參數,請求參數應該是 {"name1":"value1","name2":"value2"}的形式。
     * @param charset
     *            發送和接收的格式
     * @return URL 所表明遠程資源的響應結果
     */
    public static String sendGet(String url, Map<String, Object> map) {
        StringBuffer sb = new StringBuffer();
        // 構建請求參數
        if (map != null && map.size() > 0) {
            Iterator<?> it = map.entrySet().iterator(); // 定義迭代器
            while (it.hasNext()) {
                Map.Entry<?, ?> er = (Entry<?, ?>) it.next();
                sb.append(er.getKey());
                sb.append("=");
                sb.append(er.getValue());
                sb.append("&");
            }
        }
        return sendGet(url, sb.toString());
    }

    /**
     * 向指定URL發送POST方法的請求
     * 
     * @param url
     *            發送請求的URL
     * @param param
     *            請求Map參數,請求參數應該是 {"name1":"value1","name2":"value2"}的形式。
     * @param charset
     *            發送和接收的格式
     * @return URL 所表明遠程資源的響應結果
     */
    public static String sendPost(String url, Map<String, Object> map) {
        StringBuffer sb = new StringBuffer();
        // 構建請求參數
        if (map != null && map.size() > 0) {
            for (Entry<String, Object> e : map.entrySet()) {
                sb.append(e.getKey());
                sb.append("=");
                sb.append(e.getValue());
                sb.append("&");
            }
        }
        return sendPost(url, sb.toString());
    }

    /**
     * 向指定URL發送GET方法的請求
     * 
     * @param url
     *            發送請求的URL
     * @param param
     *            請求參數,請求參數應該是 name1=value1&name2=value2 的形式。
     * @param charset
     *            發送和接收的格式
     * @return URL 所表明遠程資源的響應結果
     */
    public static String sendGet(String url, String param) {
        String result = "";
        String line;
        StringBuffer sb = new StringBuffer();
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            URL realUrl = new URL(urlNameString);
            // 打開和URL之間的鏈接
            URLConnection conn = realUrl.openConnection();
            // 設置通用的請求屬性 設置請求格式
            conn.setRequestProperty("contentType", "utf-8");
            conn.setRequestProperty("content-type",
                    "application/x-www-form-urlencoded");
            // 設置超時時間
            conn.setConnectTimeout(600);
            conn.setReadTimeout(600);
            // 創建實際的鏈接
            conn.connect();
            // 定義 BufferedReader輸入流來讀取URL的響應,設置接收格式
            in = new BufferedReader(new InputStreamReader(
                    conn.getInputStream(), "utf-8"));
            while ((line = in.readLine()) != null) {
                sb.append(line);
            }
            result = sb.toString();
        } catch (Exception e) {
            System.out.println("發送GET請求出現異常!" + e);
            e.printStackTrace();
        }
        // 使用finally塊來關閉輸入流
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 向指定 URL 發送POST方法的請求
     * 
     * @param url
     *            發送請求的 URL
     * @param param
     *            請求參數,請求參數應該是 name1=value1&name2=value2 的形式。
     * @param charset
     *            發送和接收的格式
     * @return 所表明遠程資源的響應結果
     */
    public static String sendPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        String line;
        StringBuffer sb = new StringBuffer();
        try {
            URL realUrl = new URL(url);
            // 打開和URL之間的鏈接
            URLConnection conn = realUrl.openConnection();
            // 設置通用的請求屬性 設置請求格式
            conn.setRequestProperty("contentType", "utf-8");
            conn.setRequestProperty("content-type",
                    "application/x-www-form-urlencoded");
            // 設置超時時間
            conn.setConnectTimeout(600);
            conn.setReadTimeout(600);
            // 發送POST請求必須設置以下兩行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 獲取URLConnection對象對應的輸出流
            out = new PrintWriter(conn.getOutputStream());
            // 發送請求參數
            out.print(param);
            // flush輸出流的緩衝
            out.flush();
            // 定義BufferedReader輸入流來讀取URL的響應 設置接收格式
            in = new BufferedReader(new InputStreamReader(
                    conn.getInputStream(), "utf-8"));
            while ((line = in.readLine()) != null) {
                sb.append(line);
            }
            result = sb.toString();
        } catch (Exception e) {
            System.out.println("發送 POST請求出現異常!" + e);
            e.printStackTrace();
        }
        // 使用finally塊來關閉輸出流、輸入流
        finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return result;
    }

    public static String getRemoteIpAddr(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();
        }
    
// 若是手機是雙卡雙待,那麼會獲取到兩個IP。咱們僅須要有一個。
    return ip.split(",")[0];
  }
import java.security.MessageDigest;
 
public class MD5Util {
    private final static String[] hexDigits = { "0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };

    /**
     * MD5加密解密工具類
     * 
     * @param b
     *            字節數組
     * @return 16進制字串
     */
    public static String byteArrayToHexString(byte[] b) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++) {
            resultSb.append(byteToHexString(b[i]));
        }
        return resultSb.toString();
    }

    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n = 256 + n;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }

    public static String MD5Encode(String origin) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            resultString = byteArrayToHexString(md.digest(resultString
                    .getBytes("utf-8")));
        } catch (Exception ex) {
        }
        return resultString;
    }
    
    public static boolean isValidate(String input,String output){
        
        boolean status = false;
        
        if(MD5Util.MD5Encode(input).equals(output)){
            status = true;
        }else{
            status = false;
        }
        
        return status;
    }
    
}
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.http.HttpResponse;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.meihe.dto.wePay.WeChatConst;

@SuppressWarnings("deprecation")
public class WeChatUtil {

    private static final Logger log = LoggerFactory.getLogger(WeChatUtil.class);

    /**
     * 統一下單 得到PrePayId
     * 
     * @param body
     *            商品或支付單簡要描述
     * @param out_trade_no
     *            商戶系統內部的訂單號,32個字符內、可包含字母
     * @param total_fee
     *            訂單總金額,單位爲分
     * @param IP
     *            APP和網頁支付提交用戶端ip
     * @param notify_url
     *            接收微信支付異步通知回調地址
     * @param openid
     *            用戶openId
     * @throws Exception 
     */
    public static String unifiedorder(String body, String out_trade_no,
            Integer total_fee, String ip, String notify_url, String openId)
            throws Exception {
        log.debug(">>>>>>>>>>>>>>>>>開始準備統一下單流程....");
        
        /**
         * 組裝請求參數 按照ASCII排序
         */
        SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
        String nonce_str = getNonceStr();// 隨機數據
        parameters.put("appid", WeChatConst.APPID);
        parameters.put("body", body);
        parameters.put("mch_id", WeChatConst.MCH_ID);
        parameters.put("nonce_str", nonce_str);
        parameters.put("notify_url", notify_url);
        parameters.put("openid", openId);
        parameters.put("out_trade_no", out_trade_no);
        parameters.put("spbill_create_ip", ip);
        parameters.put("total_fee", total_fee.toString());
        parameters.put("trade_type", WeChatConst.TRADE_TYPE);
        String sign = WxSign.createSign(parameters, WeChatConst.KEY);

        /**
         * 組裝XML
         */
        log.debug(">>>>>>>>>>>>>>>>>統一下單數據準備完成,準備組裝....{}",parameters.toString());
        StringBuilder sb = new StringBuilder("");
        sb.append("<xml>");
        setXmlKV(sb, "appid", WeChatConst.APPID);
        setXmlKV(sb, "body", body);
        setXmlKV(sb, "mch_id", WeChatConst.MCH_ID);
        setXmlKV(sb, "nonce_str", nonce_str);
        setXmlKV(sb, "notify_url", notify_url);
        setXmlKV(sb, "openid", openId);
        setXmlKV(sb, "out_trade_no", out_trade_no);
        setXmlKV(sb, "spbill_create_ip", ip);
        setXmlKV(sb, "total_fee", total_fee.toString());
        setXmlKV(sb, "trade_type", WeChatConst.TRADE_TYPE);
        setXmlKV(sb, "sign", sign);
        sb.append("</xml>");
        // 這個處理是爲了防止傳中文的時候出現簽名錯誤
        StringEntity reqEntity = new StringEntity(new String(sb.toString().getBytes("UTF-8"), "ISO8859-1"));
        log.debug(">>>>>>>>>>>>>>>>>統一下單數據組裝完成,準備下單。");
        
        HttpPost httppost = new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder");
        httppost.setEntity(reqEntity);
        log.debug(">>>>>>>>>>>>>>>>>請求地址、請求數據封裝完成。開始下單。");
        
        @SuppressWarnings("resource")
        DefaultHttpClient httpclient = new DefaultHttpClient();
        HttpResponse response = httpclient.execute(httppost);
        
        log.debug(">>>>>>>>>>>>>>>>>下單請求結束。獲取返回值。");
        String strResult = EntityUtils.toString(response.getEntity(),Charset.forName("utf-8"));
        log.debug(">>>>>>>>>>>>>>>>>下單請求結束。轉換字符串返回值結束。");
        
        return strResult;

    }

    // 得到隨機字符串
    public static String getNonceStr() {
        String str = "";
        Random random = new Random();
        try {
            str = MD5Util.MD5Encode(String.valueOf(random.nextInt(10000)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return str;
    }

    // 插入XML標籤
    public static StringBuilder setXmlKV(StringBuilder sb, String Key,
            String value) {
        sb.append("<");
        sb.append(Key);
        sb.append(">");

        sb.append(value);

        sb.append("</");
        sb.append(Key);
        sb.append(">");

        return sb;
    }

    // 解析XML 得到 PrePayId
    public static String getPrePayId(String xml) {
        int start = xml.indexOf("<prepay_id>");
        int end = xml.indexOf("</prepay_id>");
        if (start < 0 && end < 0) {
            return null;
        }
        return xml.substring(start + "<prepay_id>".length(), end)
                .replace("<![CDATA[", "").replace("]]>", "");
    }

    // 商戶訂單號
    public static String getOut_trade_no() {
        DateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        return df.format(new Date()) + buildRandom(7);
    }

    // 時間戳
    public static String getTimeStamp() {
        return String.valueOf(System.currentTimeMillis() / 1000);
    }

    // 隨機4位數字
    public static int buildRandom(int length) {
        int num = 1;
        double random = Math.random();
        if (random < 0.1) {
            random = random + 0.1;
        }
        for (int i = 0; i < length; i++) {
            num = num * 10;
        }
        return (int) ((random * num));
    }

    public static String inputStream2String(InputStream inStream,
            String encoding) {
        String result = null;
        try {
            if (inStream != null) {
                ByteArrayOutputStream outStream = new ByteArrayOutputStream();
                byte[] tempBytes = new byte[1024];
                int count = -1;
                while ((count = inStream.read(tempBytes, 0, 1024)) != -1) {
                    outStream.write(tempBytes, 0, count);
                }
                tempBytes = null;
                outStream.flush();
                result = new String(outStream.toByteArray(), encoding);
            }
        } catch (Exception e) {
            result = null;
        }
        return result;
    }

}
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Properties;

/**
 * 讀取微信properties配置文件
 * 
 *
 */
public class WePropertieUtil {

    private static Properties env;

    private static Properties getEnv() {
        try {
            if (env == null) {
                env = new Properties();
                env.load(WePropertieUtil.class.getClassLoader().getResourceAsStream("config/wechat-const.properties"));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return env;
    }

    /**
     * 獲取對應的key
     * 
     * @param key
     * @return
     */
    public static String getValue(String key) {
        if (env == null) {
            return getEnv().getProperty(key);
        } else {
            return env.getProperty(key);
        }
    }

    /**
     * 獲取key對應的值,並替換其中的參數
     * 
     * @param key
     * @param arguments
     * @return
     */
    public static String getValue(String key, Object... arguments) {
        return MessageFormat.format(getValue(key), arguments);
    }
    
}
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WxSign {
    
    private static final Logger log = LoggerFactory.getLogger(WeChatUtil.class);

    /**
     * 簽名封裝類。建立簽名。
     * @param parameters
     * @param key
     * @return
     * @throws Exception
     */
    @SuppressWarnings("rawtypes")
    public static String createSign(SortedMap<Object, Object> parameters,String key) throws Exception {
        log.info(">>>>>>>>>>>>>>>>>開始封裝數據,建立簽名....");
        StringBuffer sb = new StringBuffer();
        // 全部參與傳參的參數按照accsii排序(升序)
        Set es = parameters.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            Object v = entry.getValue();
            if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=" + key);
        log.info(">>>>>>>>>>>>>>>>>封裝數據{}",sb.toString());
        String str = sb.toString();
        log.info(str);
        String sign = MD5Util.MD5Encode(str).toUpperCase();
        log.info(">>>>>>>>>>>>>>>>>封裝數據完成,簽名爲:{}",sign);
        return sign;
    }
    
}

 

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.TimeZone;

import org.apache.commons.lang3.StringUtils;

public abstract class DateTimeUtils {

    /**
     * 獲取今天日期
     * 
     * @return
     */
    public static String getToday(String format) {
        if (StringUtils.isEmpty(format)) {
            format = "yyyyMMdd";
        }
        return LocalDate.now().format(DateTimeFormatter.ofPattern(format));
    }

    /**
     * 獲取當前時間
     * 
     * @param format
     * @return
     */
    public static String getCurrentTime(String format) {
        if (StringUtils.isEmpty(format)) {
            format = "HHmmss";
        }
        return LocalTime.now().format(DateTimeFormatter.ofPattern(format));

    }
    
    /**
     * 將字符串轉換成LocalDate格式
     * 
     * @param format
     * @return
     * @throws ParseException 
     */
    public static LocalDate toLocalDate(String LocalDate) throws ParseException{
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        Date date = sdf.parse(LocalDate);
        Instant instant = date.toInstant();
        ZoneId zone = ZoneId.systemDefault();
        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
        LocalDate localDate = localDateTime.toLocalDate();
        return localDate;
    }
    
    /**
     * 將字符串轉換成LocalTime格式
     * 
     * @param format
     * @return
     * @throws ParseException 
     */
    public static LocalTime toLocalTime(String LocalTime) throws ParseException{
        SimpleDateFormat sdf = new SimpleDateFormat("HHmmss");
        sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        Date time = sdf.parse(LocalTime);
        Instant instant = time.toInstant();
        ZoneId zone = ZoneId.systemDefault();
        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
        LocalTime localTime = localDateTime.toLocalTime();
        return localTime;
    }

}

準備完成!開始進行業務操做。

代碼流程:頁面->controller->service;

 支付頁面:

    // 頁面在執行成功以前,pay方法只容許調用一次。
    clickstatus=true;
    var sign ;
    var appId ;
    var timeStamp ;
    var nonceStr ;
    var packageStr ;
    var signType,
    var url = 'http://**/cms/wepay/';
    function pay(){
        if(clickstatus){
            clickstatus=false;
            $.ajax({
                type:"post",
                url:url+'pay',
                dataType:"json",
                data:{openId:'${openId}',totalFee:totalFee,pointAmount:pointAmount,memberId:$.cookie('memberId'),cellphone: $.cookie('cellphone'),accessToken: $.cookie('accessToken')},
                success:function(data) {
                    if(data.result_code == 'SUCCESS'){
                        appId = data.appid;
                        sign = data.sign;
                        timeStamp = data.timeStamp;
                        nonceStr = data.nonce_str;
                        packageStr = data.packageStr;
                        signType = data.signType;
                        // 調用微信支付控件
                        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();
                        }
                    }else{
                        alert("統一下單失敗");
                    }
                }
            })
        }
    }

    function onBridgeReady(){
        WeixinJSBridge.invoke('getBrandWCPayRequest', {
            "appId":appId,
            "timeStamp":timeStamp,
            "nonceStr":nonceStr,
            "package": packageStr,
            "signType":signType,
            "paySign": sign
        },function (res) {
            if (res.err_msg == "get_brand_wcpay_request:ok") {
                window.location.href = url+'paySuccess';
            } else if (res.err_msg == "get_brand_wcpay_request:fail") {
                window.location.href = url+'payFailure';
            } else if (res.err_msg == "get_brand_wcpay_request:cancel") {
                alert("支付取消");
            }
        })
    }

Controller

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import com.meihe.dto.wePay.WxPaySendData;
import com.meihe.service.wePay.IPreparedToPayService;

@RestController
@RequestMapping("wepay")
public class wePayController {

    @Autowired
    private IPreparedToPayService preparedToPayService;

    /**
     *   跳轉支付界面,將code帶過去
     **/
    @ResponseBody
    @RequestMapping("toPay")
    public ModelAndView toPay(HttpServletRequest request,HttpServletResponse response){
        ModelAndView res = new  ModelAndView();
        try {
            res = preparedToPayService.getUserOpenId(request, response);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;  
    }

    /**
     *  點擊確認充值 統一下單,得到預付id(prepay_id)   
     * @param request
     * @param response
     * @return   
     */
    @ResponseBody
    @RequestMapping("pay")
    public WxPaySendData prePay(HttpServletRequest request,HttpServletResponse response,String openId, String totalFee, String pointAmount,
            String memberId){
        WxPaySendData data = new WxPaySendData();
        try {
            data = preparedToPayService.prePay(request, response, openId,totalFee,pointAmount,memberId);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;
    }
    
    /**
     * 微信支付回調接口
     * @param request
     * @param response
     * @throws Exception 
     */
    @ResponseBody
    @RequestMapping("callback")
    public void callBack(HttpServletRequest request, HttpServletResponse response) {
        try {
            preparedToPayService.callBack(request, response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 支付成功
     **/
    @ResponseBody
    @RequestMapping("paySuccess")
    public ModelAndView paySuccess(HttpServletRequest request,HttpServletResponse response){
        return new ModelAndView("wepay/paySuccess");
    }
    
    /**
     * 支付失敗
     **/
    @ResponseBody
    @RequestMapping("payFailure")
    public ModelAndView payFailure(HttpServletRequest request,HttpServletResponse response){
        return new ModelAndView("wepay/payFailure");  
    }

Service

import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

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

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.ModelAndView;

import com.alibaba.fastjson.JSON;
import com.meihe.dto.AjaxResult;
import com.meihe.dto.wePay.UserInfo;
import com.meihe.dto.wePay.WeChatConst;
import com.meihe.dto.wePay.WeixinLoginUser;
import com.meihe.dto.wePay.WxPaySendData;
import com.meihe.model.marketing.MarketingOrder;
import com.meihe.service.marketing.IMarketingOrderService;
import com.meihe.service.wePay.IOauthService;
import com.meihe.service.wePay.IPreparedToPayService;
import com.meihe.utils.wePayUtils.HttpUtil;
import com.meihe.utils.wePayUtils.WeChatUtil;
import com.meihe.utils.wePayUtils.WxSign;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;


@Service
public class PreparedToPayServiceImpl implements IPreparedToPayService {
    
    @Autowired
    private IOauthService oauth = null;
    
    @Autowired
    private IMarketingOrderService mOrder = null;

    private static final Logger log = LoggerFactory.getLogger(PreparedToPayServiceImpl.class);
    
    @Override
    public ModelAndView getUserOpenId(HttpServletRequest request,HttpServletResponse response) throws Exception {
        log.info(">>>>>>>>>>>>>>>>>準備獲取用戶code....");
        ModelAndView modelAndView = new ModelAndView();
        @SuppressWarnings("deprecation")
        String redirecUri = URLEncoder.encode(WeChatConst.BASE_URL + "toPay");
        String code = null;
        if (request.getParameter("code") != null) {
            code = request.getParameter("code");
        }
        if (code == null) {
            log.info(">>>>>>>>>>>>>>>>>用戶首次登錄,受權中...準備獲取code....");
            String siteURL = "redirect:https://open.weixin.qq.com/connect/oauth2/authorize?appid="
                    +WeChatConst.APPID+"&redirect_uri="+redirecUri+
                    "&response_type=code&scope=snsapi_userinfo&state=1234#wechat_redirect";
            return new ModelAndView(siteURL);
        }
        code = request.getParameter("code");
        log.debug(">>>>>>>>>>>>>>>>>得到用戶code[{}]成功!準備獲取用戶信息...",code);
        WeixinLoginUser weixinLoginUser = getWeixinLoginUser(code,request,response);
        if(null == weixinLoginUser){
            log.debug(">>>>>>>>>>>>>>>>>code[{}]已被使用,從新獲取code...",code);
            // 進入頁面後,若是用戶刷新,那麼會提示code被使用的錯誤。此判斷主要應對獲取用戶信息後刷新的操做。
            String siteURL = "redirect:https://open.weixin.qq.com/connect/oauth2/authorize?appid="
                    +WeChatConst.APPID+"&redirect_uri="+redirecUri+
                    "&response_type=code&scope=snsapi_userinfo&state=1234#wechat_redirect";
            return new ModelAndView(siteURL);
        }
        modelAndView.addObject("openId", weixinLoginUser.getOpenID());
        request.setAttribute("openId", weixinLoginUser.getOpenID());
        log.debug(">>>>>>>>>>>>>>>>>準備工做完成!跳轉至購買頁面...");
        String viewName = "wepay/payIndex";
        modelAndView.setViewName(viewName);
        return modelAndView;
    }
    
    /**
     * 獲取微信受權登錄用戶
     * 
     * @param code
     * @return
     * @throws Exception
     */
    private WeixinLoginUser getWeixinLoginUser(String code,HttpServletRequest request,HttpServletResponse response) throws Exception {
        String str = oauth.getToken(code, WeChatConst.APPID,WeChatConst.APP_SECRET);
        String openID = (String) JSON.parseObject(str, Map.class).get("openid");
        String accessToken = (String) JSON.parseObject(str, Map.class).get("access_token");
        String refreshToken = (String) JSON.parseObject(str, Map.class).get("refresh_token");
        if(null == openID || null == accessToken || null == refreshToken){
            return null;
        }
        log.debug(">>>>>>>>>>>>>>>>>openid[{}],access_token[{}],refresh_token[{}]..轉JSON爲String中...",openID,accessToken,refreshToken);
        // 獲取到的用戶信息。暫時沒用。後臺暫時只保存一個openId。
        // 支付只須要用戶的openId便可完成。這個方法能夠在此結束。
        UserInfo userInfo = oauth.getSnsUserInfo(openID, accessToken);
        WeixinLoginUser weixinLoginUser = new WeixinLoginUser();
        weixinLoginUser.setOpenID(openID);
        weixinLoginUser.setUnionID(userInfo.getUnionid());
        weixinLoginUser.setHeadImageUrl(userInfo.getHeadimgurl());
        weixinLoginUser.setNickName(userInfo.getNickname());
        weixinLoginUser.setRefreshToken(refreshToken);
        log.debug(">>>>>>>>>>>>>>>>>獲取用戶信息中完成!");
        return weixinLoginUser;
    }

    @Override
    public WxPaySendData prePay(HttpServletRequest request,HttpServletResponse response, String openId, String totalFee, String pointAmount, String memberId) throws Exception {
        log.debug(">>>>>>>>>>>>>>>>>預備工做完成。開始準備預付訂單信息...");
        WxPaySendData result = new WxPaySendData();
        MarketingOrder marketingOrder = new MarketingOrder();
        // 商戶訂單號 
            String out_trade_no = WeChatUtil.getOut_trade_no();
            // 產品價格,單位:分
            Double total_fee = Double.parseDouble(totalFee)*100;
            // 客戶端IP
            String ip = HttpUtil.getRemoteIpAddr(request);
            // 支付成功後回調的url地址
            String notify_url = WeChatConst.BASE_URL+"callback";
            // 商品或支付產品的簡要描述
            String body = pointAmount+"積分";
            log.debug(">>>>>>>>>>>>>>>>>預付訂單信息準備完成。");
            log.debug(">>>>>>>>>>>>>>>>>訂單號:[{}],價格:[{}],客戶端IP[{}],回調URL:[{}],商品支付信息:[{}]",out_trade_no,total_fee,ip,notify_url,body);
            //統一下單
            String strResult = WeChatUtil.unifiedorder(body, out_trade_no, total_fee.intValue(), ip, notify_url,openId);
        
            log.debug(">>>>>>>>>>>>>>>>>解析統一下單XML....");
            XStream stream = new XStream(new DomDriver());
            stream.alias("xml", WxPaySendData.class);
            WxPaySendData wxReturnData = (WxPaySendData)stream.fromXML(strResult);
            log.debug(">>>>>>>>>>>>>>>>>統一下單XML解析完成....[{}]",wxReturnData.toString());
            
            //二者都爲SUCCESS才能獲取prepay_id
            if(wxReturnData.getResult_code().equals("SUCCESS") &&wxReturnData.getReturn_code().equals("SUCCESS") ){
                log.debug(">>>>>>>>>>>>>>>>>統一下單請求成功!開始準備正式支付....");
                //時間戳、隨機字符串
                String timeStamp = WeChatUtil.getTimeStamp();
            String nonce_str = WeChatUtil.getNonceStr();
            // 注:上面這兩個參數,必定要拿出來做爲後續的value,不能每步都建立新的時間戳跟隨機字符串,否則H5調支付API,會報簽名參數錯誤
            result.setResult_code(wxReturnData.getResult_code());
            result.setAppid(WeChatConst.APPID);
            result.setTimeStamp(timeStamp);
            result.setNonce_str(nonce_str);
            result.setPackageStr("prepay_id="+wxReturnData.getPrepay_id());
            result.setSignType("MD5");
            
            // 第二次簽名,將微信返回的數據再進行簽名
            SortedMap<Object,Object> signMap = new TreeMap<Object,Object>();
            signMap.put("appId", WeChatConst.APPID);
            signMap.put("nonceStr", nonce_str);
            // 值爲:prepay_id=xxx
            signMap.put("package", "prepay_id="+wxReturnData.getPrepay_id());  
            signMap.put("signType", "MD5");
            signMap.put("timeStamp", timeStamp);
            log.debug(">>>>>>>>>>>>>>>>>根據預支付訂單號再次進行簽名....");
            String paySign = WxSign.createSign(signMap, WeChatConst.KEY);
            log.debug(">>>>>>>>>>>>>>>>>簽名完成!準備生成本地支付訂單....");
            result.setSign(paySign);
        } else {
            result.setResult_code("fail");
        }
            log.debug(">>>>>>>>>>>>>>>>>開始生成一條新的本地支付訂單。");
          marketingOrder.setPaymentType(1);
          marketingOrder.setMemberId(Integer.parseInt(memberId));
          marketingOrder.setOutTradeNo(out_trade_no);
          marketingOrder.setTotalFee(total_fee.toString());
          marketingOrder.setPointAmount(pointAmount);
          marketingOrder.setOpenId(openId);
          AjaxResult ajaxResult = mOrder.insertMarketingOrder(marketingOrder);
          if(!ajaxResult.getSuccess()){
              result.setResult_code("fail");
          }
          log.debug(">>>>>>>>>>>>>>>>>開始正式支付!");
            return result;
    }
    
    @Override
    public void callBack(HttpServletRequest request, HttpServletResponse response) throws Exception{
        log.debug(">>>>>>>>>>>>>>>>>微信開始回調本地。校驗簽名。準備校驗簽名....");
        response.setContentType("text/xml;charset=UTF-8");
        try {
            InputStream is = request.getInputStream();
            String result = IOUtils.toString(is, "UTF-8");
            if("".equals(result)){
                response.getWriter().write("<xm><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[參數錯誤!]]></return_msg></xml>");
                return ;
            }
            // 解析xml
            XStream stream = new XStream(new DomDriver());
            stream.alias("xml", WxPaySendData.class);
            WxPaySendData wxPaySendData = (WxPaySendData)stream.fromXML(result);
            System.out.println(wxPaySendData.toString());
                
            String appid = wxPaySendData.getAppid();
            String mch_id =wxPaySendData.getMch_id();
            String nonce_str = wxPaySendData.getNonce_str();
            String out_trade_no = wxPaySendData.getOut_trade_no();
            String total_fee = wxPaySendData.getTotal_fee();
            String trade_type = wxPaySendData.getTrade_type();
            String openid =wxPaySendData.getOpenid();
            String return_code = wxPaySendData.getReturn_code();
            String result_code = wxPaySendData.getResult_code();
            String bank_type = wxPaySendData.getBank_type();
            Integer cash_fee = wxPaySendData.getCash_fee();
            String fee_type = wxPaySendData.getFee_type();
            String is_subscribe = wxPaySendData.getIs_subscribe();
            String time_end = wxPaySendData.getTime_end();
            String transaction_id = wxPaySendData.getTransaction_id();
            String sign = wxPaySendData.getSign();
            
            //簽名驗證
            SortedMap<Object,Object> parameters = new TreeMap<Object,Object>();
            parameters.put("appid",appid);
            parameters.put("mch_id",mch_id);
            parameters.put("nonce_str",nonce_str);
            parameters.put("out_trade_no",out_trade_no);
            parameters.put("total_fee",total_fee);
            parameters.put("trade_type",trade_type);
            parameters.put("openid",openid);
            parameters.put("return_code",return_code);
            parameters.put("result_code",result_code);
            parameters.put("bank_type",bank_type);
            parameters.put("cash_fee",cash_fee);
            parameters.put("fee_type",fee_type);
            parameters.put("is_subscribe",is_subscribe);
            parameters.put("time_end",time_end);
            parameters.put("transaction_id",transaction_id);
            
            String sign2 = WxSign.createSign(parameters, WeChatConst.KEY);
            
            if(sign.equals(sign2)){
                log.debug(">>>>>>>>>>>>>>>>>校驗簽名完成。正常交易。開始校驗支付狀態...");
                if(return_code.equals("SUCCESS") && result_code.equals("SUCCESS")){
                    log.debug(">>>>>>>>>>>>>>>>>校驗支付狀態完成。支付成功!");
                    // 業務邏輯(先判斷數據庫中訂單號是否存在,而且訂單狀態爲未支付狀態)
                    MarketingOrder marketingOrder = new MarketingOrder();
                    marketingOrder.setOutTradeNo(out_trade_no);
                    marketingOrder.setPayAmount(total_fee);
                    AjaxResult affordOrder = mOrder.affordOrder(marketingOrder);
                    if(!affordOrder.getSuccess()){
                        response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[交易失敗]]></return_msg></xml>");
                    }
                    // TODO:這個有什麼用嗎?暫未肯定...
                    request.setAttribute("out_trade_no", out_trade_no);
                    // 通知微信.異步確認成功.必寫.否則會一直通知後臺.八次以後就認爲交易失敗了.
                    log.debug(">>>>>>>>>>>>>>>>>本地異步確認支付成功!交易結束!");
                    response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
                    }else{
                        log.debug(">>>>>>>>>>>>>>>>>本地異步確認支付失敗!交易結束!");
                        response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[交易失敗]]></return_msg></xml>");
                    }
                }else{
                    log.debug(">>>>>>>>>>>>>>>>>校驗簽名完成。異常交易!結束交易流程。");
                    response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[簽名校驗失敗]]></return_msg></xml>");
                }
                response.getWriter().flush();
                response.getWriter().close();
                return ;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
}
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSONObject;
import com.meihe.dto.wePay.UserInfo;
import com.meihe.service.wePay.IOauthService;
import com.meihe.utils.wePayUtils.HttpUtil;

@Service
public class OauthServiceImpl implements IOauthService {
    
    private static final Logger log = LoggerFactory.getLogger(PreparedToPayServiceImpl.class);
    
//    private static final String CODE_URI = "http://open.weixin.qq.com/connect/oauth2/authorize";
    private static final String TOKEN_URI = "https://api.weixin.qq.com/sns/oauth2/access_token";
    private static final String REFRESH_TOKEN_URI = "https://api.weixin.qq.com/sns/oauth2/refresh_token";
    private static final String SNS_USER_INFO_URL = "https://api.weixin.qq.com/sns/userinfo";

    public String getToken(String code, String appid, String secret) throws Exception {
        log.debug(">>>>>>>>>>>>>>>>>準備根據code獲取用戶open_id/accessToken..",code,appid,secret);
        Map<String,Object> params = new HashMap<String,Object>();
        params.put("appid", appid);
        params.put("secret", secret);
        params.put("code", code);
        params.put("grant_type", "authorization_code");
        String result = HttpUtil.sendGet(TOKEN_URI,params);
        log.debug(">>>>>>>>>>>>>>>>>獲取信息完成[{}]...",result);
        return result;
    }

    public String getRefreshToken(String refreshToken, String appid,String secret) throws Exception {
        Map<String,Object> params = new HashMap<String,Object>();
        params.put("appid", appid);
        params.put("grant_type", "refresh_token");
        params.put("refresh_token", refreshToken);
        return HttpUtil.sendGet(REFRESH_TOKEN_URI, params);
    }

    public UserInfo getSnsUserInfo(String openid, String accessToken)throws Exception {
        Map<String,Object> params = new HashMap<String,Object>();
        log.info(">>>>>>>>>>>>>>>>>轉格式成功!準備根據open_id/accessToken獲取用戶信息...!");
        params.put("access_token", accessToken);
        params.put("openid", openid);
        params.put("lang", "zh_CN");
        log.debug(">>>>>>>>>>>>>>>>>token:[{}],openid[{}].開始鏈接微信獲取信息..",accessToken,openid);
        String jsonStr = HttpUtil.sendGet(SNS_USER_INFO_URL, params);
        log.debug(">>>>>>>>>>>>>>>>>成功獲取信息[{}]。轉JSON爲UserInfo..",jsonStr);
        if (StringUtils.isNotEmpty(jsonStr)) {
            JSONObject obj = JSONObject.parseObject(jsonStr);
            if (obj.get("errcode") != null) {
                throw new Exception(obj.getString("errmsg"));
            }
            UserInfo user = (UserInfo) JSONObject.toJavaObject(obj,UserInfo.class);
            log.debug(">>>>>>>>>>>>>>>>>轉UserInfo格式完成!");
            return user;
        }
        return null;
    }
}
相關文章
相關標籤/搜索