1. 程式人生 > >微信公眾號支付開發 --Java

微信公眾號支付開發 --Java

  /**
     * 付款頁面
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @RequestMapping("/callback")
    public String pay(HttpServletRequest request, HttpServletResponse response) throws Exception {
        LogUtils.trace("-----------------------/pay/callback/--------------------------------");

        /**
         * 獲得code和state欄位
         */
        String code = request.getParameter("code");
        String state = request.getParameter("state");
        int total = (int)(Float.parseFloat(state)*100);

        /**
         * 獲取openId
         */
        WxOauthAccessToken oauthAccessToken = PayUtil.getOauthAccessToken(code);
        String openId = oauthAccessToken.getOpenId();


        /**
         * 執行統一下單介面,初始化各引數
         */
        Date now = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        String outTradeNo = "NO" + dateFormat.format( now );
        String body = "測試";
        String nonceStr = StringUtil.generateRandomString(16);
//        String spBillCreateIP = ReqUtils.getRealIp(request);
        String spBillCreateIP = "127.0.0.1";

        //初始化傳入引數
        JsApiReqData jsApiReqData =
                new JsApiReqData(body,nonceStr,outTradeNo,total,spBillCreateIP, openId);

        String info = MobiMessage.JsApiReqData2xml(jsApiReqData).replaceAll("__", "_");
        LogUtils.trace(info);

        String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        StringBuffer sb = JsApiReqUtils.httpsRequest(url, "POST", info);
        if (!"".equals(sb.toString())) {
            /**
             * 得到預支付ID,即prepay_id
             */
            Map<String, String> getMap = MobiMessage.parseXml(new String(sb.toString().getBytes(), "utf-8"));
            LogUtils.trace(getMap);
            String prepay_id = getMap.get("prepay_id");
            LogUtils.trace(prepay_id);

            // 生成支付簽名,這個簽名給微信支付的呼叫使用
            Integer timeStamp = DateUtils.getCurrentTimestamp();
            JsApiToJsData jsApiToJsData = new JsApiToJsData(timeStamp, nonceStr,prepay_id);

            String redirectUrl = "/#pay?appId=APPID&timeStamp=TIMESTAMP&nonceStr=NONCESTR&prepay_id=PREPAYID&signType=MD5&paySign=SIGN";
            redirectUrl = redirectUrl.replace("APPID", jsApiToJsData.getAppid())
                    .replace("TIMESTAMP", jsApiToJsData.getTimeStamp() + "")
                    .replace("NONCESTR", jsApiToJsData.getNonce_str())
                    .replace("PREPAYID", jsApiToJsData.getPrepay_id())
                    .replace("SIGN", jsApiToJsData.getSign());
            LogUtils.trace(redirectUrl);

            return "redirect:" + redirectUrl ;
        } else {
            LogUtils.trace("統一下單失敗!");
            return "";
        }
    }

/**
 * 請求公眾號支付API需要提交的資料
 * @author Created by fenghui.
 * @date Created on 2016/09/06/9:28
 **/
public class JsApiReqData {
    //每個欄位具體的意思請檢視API文件
    private String appid = "";
    private String mch_id = "";
    private String body = "";
    private String nonce_str = "";
    private String sign = "";
    private String notify_url = "";
    private String out_trade_no = "";
    private String spbill_create_ip = "";
    private Integer total_fee = 0;
    private String trade_type = "";
    private String openid = "";

    /**
     * @param body   要支付的商品的描述資訊,使用者會在支付成功頁面裡看到這個資訊
     * @param nonceStr  隨機字串,不長於32 位
     * @param outTradeNo  商戶系統內部的訂單號,32個字元內可包含字母, 確保在商戶系統唯一
     * @param totalFee   訂單總金額,單位為“分”,只能整數
     * @param spBillCreateIP  訂單生成的機器IP
     * @param openid  使用者標識,trade_type=JSAPI,此引數必傳,使用者在商戶appid下的唯一標識。
     */
    public JsApiReqData(String body,String nonceStr,String outTradeNo,Integer totalFee,String spBillCreateIP,
                        String openid){

        //微信分配的公眾號ID(開通公眾號之後可以獲取到)
        setAppid(WxConfigure.AppId);

        //微信支付分配的商戶號ID(開通公眾號的微信支付功能之後可以獲取到)
        setMch_id(WxConfigure.Mch_id);

        //接收微信支付非同步通知後的回撥地址
        setNotify_url(WxConfigure.Notify_url);

        //交易型別,取值如下:JSAPI,NATIVE,APP,
        setTrade_type(WxConfigure.Trade_type);

        //要支付的商品的描述資訊,使用者會在支付成功頁面裡看到這個資訊
        setBody(body);

        //商戶系統內部的訂單號,32個字元內可包含字母, 確保在商戶系統唯一
        setOut_trade_no(outTradeNo);

        //訂單總金額,單位為“分”,只能整數
        setTotal_fee(totalFee);

        //訂單生成的機器IP
        setSpbill_create_ip(spBillCreateIP);

        //隨機字串,不長於32 位
        setNonce_str(nonceStr);

        //使用者標識,trade_type=JSAPI,此引數必傳,使用者在商戶appid下的唯一標識。
        setOpenid(openid);


        //根據API給的簽名規則進行簽名
        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("body", body);
        parameters.put("out_trade_no", out_trade_no);
        parameters.put("notify_url", notify_url);
        parameters.put("total_fee", total_fee);
        parameters.put("spbill_create_ip", spbill_create_ip);
        parameters.put("trade_type", trade_type);
        parameters.put("openid", openid);

        String sign = DictionarySort.createSign(parameters);
        //根據給的簽名規則進行簽名
        setSign(sign);//把簽名資料設定到Sign這個屬性中

    }

public class JsApiToJsData {
    //每個欄位具體的意思請檢視API文件
    private String appid = "";
    private Integer timeStamp = 0;
    private String nonce_str = "";
    private String sign = "";
    private String prepay_id = "";
    private String signType = "";

    /**
     * @param nonceStr  隨機字串,不長於32 位
     */
    public JsApiToJsData(Integer timeStamp,String nonceStr,String prepay_id){

        //微信分配的公眾號ID(開通公眾號之後可以獲取到)
        setAppid(WxConfigure.AppId);

        setTimeStamp(timeStamp);

        setNonce_str(nonceStr);

        setPrepay_id(prepay_id);

        setSignType("MD5");

        //根據API給的簽名規則進行簽名
        SortedMap<Object,Object> parameters = new TreeMap<Object,Object>();
        parameters.put("appId", appid);
        parameters.put("timeStamp", timeStamp);
        parameters.put("nonceStr", nonceStr);
        parameters.put("package", "prepay_id=" + prepay_id);
        parameters.put("signType",signType);

        //根據給的簽名規則進行簽名
        String sign = DictionarySort.createSign(parameters);
        setSign(sign);//把簽名資料設定到Sign這個屬性中
    }
    類StringUtil主要用於產生隨機字串。
public class StringUtil {

    public static String generateRandomString() {
        return generateRandomString(16);
    }

    public static String generateRandomString(int length) {
        String seekChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        int seekLength = seekChars.length();
        String str = "";
        for (int i = 0; i < length; i++) {
            str += seekChars.charAt((int) (Math.random() * seekLength));
        }
        return str;
    }

    public static String generateRandomNumber() {
        return generateRandomNumber(6);
    }

    public static String generateRandomNumber(int length) {
        String seekChars = "0123456789";
        int seekLength = seekChars.length();
        String str = "";
        for (int i = 0; i < length; i++) {
            str += seekChars.charAt((int) (Math.random() * seekLength));
        }
        return str;
    }
}
    類JsApiReqUtils用於獲得網頁支付提交使用者端ip和觸發http請求。
public class JsApiReqUtils {

    //獲取真實IP
    public static String getRealIp(HttpServletRequest request) {
        if (request == null) {
            LogUtils.error("getRealIp request null");
            return "0.0.0.0";
        }
        String realIp = request.getHeader("X-Real-IP");
        LogUtils.trace("realIp:" + realIp);
        if (realIp != null && realIp.length() > 0) {
            return realIp;
        }
        realIp = request.getHeader("X-Forwarded-For");
        LogUtils.trace("realIp2:" + realIp);
        if (realIp != null && realIp.length() > 0) {
            return realIp;
        }
        return request.getRemoteAddr();
    }

    public static StringBuffer httpsRequest(String requestUrl, String requestMethod, String output)
            throws Exception {
        HttpURLConnection conn = (HttpURLConnection) new URL(requestUrl).openConnection();
        //加入資料
        conn.setRequestMethod(requestMethod);
        conn.setDoOutput(true);

        BufferedOutputStream buffOutStr = new BufferedOutputStream(conn.getOutputStream());
        buffOutStr.write(output.getBytes("utf-8"));
        buffOutStr.flush();
        buffOutStr.close();

        //獲取輸入流
        BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));

        String line = null;
        StringBuffer sb = new StringBuffer();
        while((line = reader.readLine())!= null){
            sb.append(line);
        }
        return sb;
    }

}
    MobiMessage類主要用於把json格式的資料轉換為微信需要的xml格式的資料,並把返回值從xml格式解析為map格式。
public class MobiMessage {

    public static Map<String,String> xml2map(HttpServletRequest request) throws IOException, DocumentException {
        Map<String,String> map = new HashMap<String, String>();
        SAXReader reader = new SAXReader();
        InputStream inputStream = request.getInputStream();
        Document document = reader.read(inputStream);
        Element root = document.getRootElement();
        List<Element> list = root.elements();
        for(Element e:list){
            map.put(e.getName(), e.getText());
        }
        inputStream.close();
        return map;
    }


    //訂單轉換成xml
    public static String JsApiReqData2xml(JsApiReqData jsApiReqData){
        /*XStream xStream = new XStream();
        xStream.alias("xml",productInfo.getClass());
        return xStream.toXML(productInfo);*/
        MobiMessage.xstream.alias("xml",jsApiReqData.getClass());
        return MobiMessage.xstream.toXML(jsApiReqData);
    }

    public static String RefundReqData2xml(RefundReqData refundReqData){
        /*XStream xStream = new XStream();
        xStream.alias("xml",productInfo.getClass());
        return xStream.toXML(productInfo);*/
        MobiMessage.xstream.alias("xml",refundReqData.getClass());
        return MobiMessage.xstream.toXML(refundReqData);
    }

    public static String class2xml(Object object){

        return "";
    }
    public static Map<String, String> parseXml(String xml) throws Exception {
        Map<String, String> map = new HashMap<String, String>();
        Document document = DocumentHelper.parseText(xml);
        Element root = document.getRootElement();
        List<Element> elementList = root.elements();
        for (Element e : elementList)
            map.put(e.getName(), e.getText());
        return map;
    }

    //擴充套件xstream,使其支援CDATA塊
    private static XStream xstream = new XStream(new XppDriver() {
        public HierarchicalStreamWriter createWriter(Writer out) {
            return new PrettyPrintWriter(out) {
                // 對所有xml節點的轉換都增加CDATA標記
                boolean cdata = true;

                //@SuppressWarnings("unchecked")
                public void startNode(String name, Class clazz) {
                    super.startNode(name, clazz);
                }

                protected void writeText(QuickWriter writer, String text) {
                    if (cdata) {
                        writer.write("<![CDATA[");
                        writer.write(text);
                        writer.write("]]>");
                    } else {
                        writer.write(text);
                    }
                }
            };
        }
    });


}
    DateUtils類主要用於獲得當前的時間戳。
public class DateUtils {


    public static int minTimestamp = 0; //最小的時間戳
    public static int maxTimestamp = 1999999999;//最大的時間戳
    public static int offset = 0;

    /**
     * 按照yyyy-MM-dd HH:mm:ss的格式,日期轉字串
     *
     * @param date
     * @return yyyy-MM-dd HH:mm:ss
     */
    public static String date2Str(Date date) {
        return date2Str(date, "yyyy-MM-dd HH:mm:ss");
    }

    /**
     * 按照引數format的格式,日期轉字串
     *
     * @param date
     * @param format
     * @return
     */
    public static String date2Str(Date date, String format) {
        if (date != null) {
            SimpleDateFormat sdf = new SimpleDateFormat(format);
            return sdf.format(date);
        } else {
            return "";
        }
    }

    public static int getCurrentTimestamp() {
        return (int) (System.currentTimeMillis() / 1000 + offset);
    }

    public static boolean setCurrentTimestamp(int timestamp) {
        int systemTimestamp = (int) (System.currentTimeMillis() / 1000);
        offset = timestamp - systemTimestamp;
        return true;

    }
}