1. 程式人生 > >java實現微信h5支付

java實現微信h5支付

微信h5支付需要在微信商戶平臺-》產品中心開通h5支付。
這裡寫圖片描述
官網提供的開發文件中需要的引數:
這裡寫圖片描述這裡寫圖片描述這裡寫圖片描述
h5支付主要是scene_info中的引數wap_url必須是可以訪問到的地址。spbill_create_ip的獲取必須和調起微信支付的ip一致。
程式碼實現如下:
action中程式碼:

private static DecimalFormat df = new DecimalFormat("0.00");
 @Action("weChatUnifiedorder")
   public String weChatUnifiedorder(){
       try {
df.setRoundingMode
(RoundingMode.HALF_UP); // 獲取使用者ID String memberId = Struts2Utils.getRequest().getParameter("memberId"); String integer = Struts2Utils.getRequest().getParameter("integer"); String type = Struts2Utils.getRequest().getParameter("type"); //我這裡的ip直接從前端頁面獲取部分手機不能獲取 String spbill_create_ip = Struts2Utils.getRequest
().getParameter("ip"); if (StringUtils.isNotBlank(memberId) && StringUtils.isNotBlank(integer) && StringUtils.isNotBlank(type)) { Integer score = Integer.valueOf(integer); // 產生訂單號 String outTradeNo = UuIdUtils.getUUID
(); // WeixinConfigUtils config = new WeixinConfigUtils(); // 引數組 String appid = config.appid; String mch_id = config.mch_id; String nonce_str = RandCharsUtils.getRandomString(16); String body = ""; if (type.equals("0")) { body = "購買" + score + "積分,支付" + df.format(score / 100) + "元"; } else if (type.equals("1")) { body = "購買商品支付" + String.valueOf(score / 100) + "元"; } String attach = "暫時無用(這個用於區分不同版本)"; String out_trade_no = outTradeNo; int total_fee = score;// 單位是分,現在按照ios傳遞過來的引數進行 String notify_url = config.notify_url; String trade_type = "MWEB"; // 引數:開始生成第一次簽名 parameters.put("appid", appid); parameters.put("mch_id", mch_id); parameters.put("body", body); parameters.put("nonce_str", nonce_str); parameters.put("attach", attach); parameters.put("out_trade_no", out_trade_no); parameters.put("total_fee", total_fee); parameters.put("notify_url", notify_url); parameters.put("trade_type", trade_type); parameters.put("spbill_create_ip", spbill_create_ip); parameters.put("scene_info", "'h5_info':{'type':'Wap','wap_url':'www.abc.com(必須可以直接訪問,**這裡不要帶http://**)','wap_name': '應用名(會在微信訂單頁顯示)'}"); String sign = WXSignUtils.createSign("UTF-8", parameters); // 微信統一下單 unifiedorder.setAppid(appid); unifiedorder.setMch_id(mch_id); unifiedorder.setNonce_str(nonce_str); unifiedorder.setSign(sign); unifiedorder.setBody(body); unifiedorder.setAttach(attach); unifiedorder.setOut_trade_no(out_trade_no); unifiedorder.setTotal_fee(total_fee); unifiedorder.setSpbill_create_ip(spbill_create_ip); unifiedorder.setNotify_url(notify_url); unifiedorder.setTrade_type(trade_type); unifiedorder.setScene_info("'h5_info':{'type':'Wap','wap_url':'http://www.abc.com(必須可以直接訪問**這裡不要帶http://**)','wap_name': '應用名(會在微信訂單頁顯示)'}"); Map<String, String> msgMap = HttpXmlUtils.getUrl(unifiedorder); if (msgMap.get("return_code").equals("SUCCESS") && msgMap.get("result_code").equals("SUCCESS")) { aLiPay.setApp_id(appid); aLiPay.setSubject(body); aLiPay.setScore(integer); aLiPay.setGmt_create(simpleDateFormat.format(new Date())); if (null != memberId && !"".equals(memberId)) { aLiPay.setMember(memberService.getMemberById(Integer .valueOf(memberId))); } aLiPay.setOut_trade_no(out_trade_no); aLiPay.setPayStatus(PayStatusEnum.WAITPAY.getComment()); aLiPay.setPayType(PayTypeEnum.WEIXIN.getComment()); aLiPay.setState(0); aLiPay.setTotal_amount(String.valueOf(total_fee / 100)); aLiPay.setSign(sign); aLiPay.setTotalFee(total_fee); aLiPayService.addALiPay(aLiPay); msg.put("ok", true); //將回調地址發回前端,支付完成後會自動跳轉該頁面(回撥url必須帶http://) msg.put("url", msgMap.get("mweb_url")+"&redirect_url=http://www.jabc.com/member/toMemberIntegral.do"); Struts2Utils.renderJson(msg); }else { msg.put("ok", false); } } return NONE; } catch (Exception e) { log.error(e.getMessage()); return "500"; } }

微信支付簽名程式碼:

public class WXSignUtils {
    /**
     * 微信支付簽名演算法sign
     * @param characterEncoding
     * @param parameters
     * @return
     */
    @SuppressWarnings("rawtypes")
    public static String createSign(String characterEncoding,SortedMap<Object,Object> parameters){
        StringBuffer sb = new StringBuffer();
        Set es = parameters.entrySet();//所有參與傳參的引數按照accsii排序(升序)
        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=" + weixinConstant.KEY);
        String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
        return sign;
    }
}

向微信傳送post資料並解析返回的微信訂單號

public class HttpXmlUtils {
/**
     * h5支付時 解析返回的值並返回prepareid
     * @throws IOException 
     * @throws JDOMException 
     */
    public static Map<String, String> getUrl(Unifiedorder unifiedorder) throws JDOMException, IOException{
    //構造xml
        String xmlInfo = HttpXmlUtils.xmlH5Info(unifiedorder);
        String wxUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        String method = "POST";
        //獲取微信返回的結果
        String weixinPost = HttpXmlUtils.httpsRequest(wxUrl, method, xmlInfo).toString();
        //解析返回的xml
        ParseXMLUtils.jdomParseXml(weixinPost);
        StringReader read = new StringReader(weixinPost);
        // 建立新的輸入源SAX 解析器將使用 InputSource 物件來確定如何讀取 XML 輸入
        InputSource source = new InputSource(read);
        // 建立一個新的SAXBuilder
        SAXBuilder sb = new SAXBuilder();
        // 通過輸入源構造一個Document
        org.jdom.Document doc;
        doc = (org.jdom.Document) sb.build(source);
        org.jdom.Element root = doc.getRootElement();// 指向根節點
        List<org.jdom.Element> list = root.getChildren();
        String prepayId =null;
        Map<String, String> msg = new HashMap<String, String>();
        if(list!=null&&list.size()>0){
            for (org.jdom.Element element : list) {
            msg.put(element.getName(), element.getText());
            }
            }
        return msg;
    }
    /**
     * 構造xml引數
     * @param xml
     * @return
     */
    public static String xmlH5Info(Unifiedorder unifiedorder){
        if(unifiedorder!=null){
            StringBuffer bf = new StringBuffer();
            bf.append("<xml>");

            bf.append("<appid><![CDATA[");
            bf.append(unifiedorder.getAppid());
            bf.append("]]></appid>");

            bf.append("<mch_id><![CDATA[");
            bf.append(unifiedorder.getMch_id());
            bf.append("]]></mch_id>");

            bf.append("<nonce_str><![CDATA[");
            bf.append(unifiedorder.getNonce_str());
            bf.append("]]></nonce_str>");

            bf.append("<sign><![CDATA[");
            bf.append(unifiedorder.getSign());
            bf.append("]]></sign>");

            bf.append("<body><![CDATA[");
            bf.append(unifiedorder.getBody());
            bf.append("]]></body>");


            bf.append("<attach><![CDATA[");
            bf.append(unifiedorder.getAttach());
            bf.append("]]></attach>");

            bf.append("<out_trade_no><![CDATA[");
            bf.append(unifiedorder.getOut_trade_no());
            bf.append("]]></out_trade_no>");

            bf.append("<total_fee><![CDATA[");
            bf.append(unifiedorder.getTotal_fee());
            bf.append("]]></total_fee>");

            bf.append("<spbill_create_ip><![CDATA[");
            bf.append(unifiedorder.getSpbill_create_ip());
            bf.append("]]></spbill_create_ip>");

            bf.append("<notify_url><![CDATA[");
            bf.append(unifiedorder.getNotify_url());
            bf.append("]]></notify_url>");

            bf.append("<trade_type><![CDATA[");
            bf.append(unifiedorder.getTrade_type());
            bf.append("]]></trade_type>");

            bf.append("<scene_info><![CDATA[");
            bf.append(unifiedorder.getScene_info());
            bf.append("]]></scene_info>");

            bf.append("</xml>");
            return bf.toString();
        }
        return "";
    }
/**
     * post請求並得到返回結果
     * @param requestUrl
     * @param requestMethod
     * @param output
     * @return
     */
    public static String httpsRequest(String requestUrl, String requestMethod, String output) {
        try{
            URL url = new URL(requestUrl);
            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setUseCaches(false);
            connection.setRequestMethod(requestMethod);
            if (null != output) {
                OutputStream outputStream = connection.getOutputStream();
                outputStream.write(output.getBytes("UTF-8"));
                outputStream.close();
            }
            // 從輸入流讀取返回內容
            InputStream inputStream = connection.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String str = null;
            StringBuffer buffer = new StringBuffer();
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }
            bufferedReader.close();
            inputStreamReader.close();
            inputStream.close();
            inputStream = null;
            connection.disconnect();
            return buffer.toString();
        }catch(Exception ex){
            ex.printStackTrace();
        }
        return "";
    }
}

解析微信返回的xml

/**
     * 3、JDOM解析XML
     * 解析的時候自動去掉CDMA
     * @param xml
     */
    @SuppressWarnings("unchecked")
    public static void jdomParseXml(String xml){
        try { 
            StringReader read = new StringReader(xml);
            // 建立新的輸入源SAX 解析器將使用 InputSource 物件來確定如何讀取 XML 輸入
            InputSource source = new InputSource(read);
            // 建立一個新的SAXBuilder
            SAXBuilder sb = new SAXBuilder();
            // 通過輸入源構造一個Document
            org.jdom.Document doc;
            doc = (org.jdom.Document) sb.build(source);

            org.jdom.Element root = doc.getRootElement();// 指向根節點
            List<org.jdom.Element> list = root.getChildren();

            if(list!=null&&list.size()>0){
                for (org.jdom.Element element : list) {
                }
            }

        } catch (JDOMException e) {
            e.printStackTrace();
        }  catch (IOException e) {
            e.printStackTrace();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

封裝支付引數的實體類:(引數名與官網引數名一致,此類封裝了h5、app支付和退款的引數)

import java.io.Serializable;

/**
 * 統一下單提交為微信的引數
 * @author 
 * @date 2017年08月11日
 */
public class Unifiedorder implements Serializable{
    private static final long serialVersionUID = 1L;
    //微信支付表id
    private Integer weixinId;
    //微信分配的公眾賬號ID(企業號corpid即為此appId)
    private String appid;
    //商戶id
    private String mch_id;
    //終端裝置號(門店號或收銀裝置ID),注意:PC網頁或公眾號內支付請傳"WEB"
    private String device_info;
    //隨機字串:數字+大寫字母的組合,32位
    private String nonce_str;
    //簽名
    private String sign;
    //商品或支付單簡要描述
    private String body;
    //商品名稱明細列表
    private String detail;
    //附加引數(例如:用於區別本商戶不同的分店)
    private String attach;
    //商戶系統內部的訂單號
    private String out_trade_no;
    //貨幣型別:符合ISO 4217標準的三位字母程式碼,預設人民幣:CNY
    private String fee_type;
    //總金額
    private int total_fee;
    //APP和網頁支付提交[使用者端ip],Native支付填呼叫微信支付API的機器IP。
    private String spbill_create_ip;
    //訂單生成時間,格式為yyyyMMddHHmmss,
    private String time_start;
    //訂單失效時間,格式為yyyyMMddHHmmss,最短失效時間間隔必須大於5分鐘[支付寶是30分鐘,同樣30分鐘]
    private String time_expire;
    //商品標記,代金券或立減優惠功能的引數
    private String goods_tag;
    //接收微信支付非同步通知回撥地址
    private String notify_url;
    //交易型別:JSAPI,NATIVE,APP h5為 MWEB
    private String trade_type;
    //trade_type=NATIVE,此引數必傳。此id為二維碼中包含的商品ID,商戶自行定義。
    private String product_id;
    //no_credit--指定不能使用信用卡支付
    private String limit_pay;
    //trade_type=JSAPI,此引數必傳,使用者在商戶appid下的唯一標識
    private String openid;
    //商戶內部自己的退款單號
    private String out_refund_no;
    //退款總金額單位為分
    private int refund_fee;
    //操作員的id預設為mch_id
    private String op_user_id;
    //微信官方提供的訂單號
    private String prepayid;
    //記錄所對應的member
    private Member member;
    //返回給微信的狀態碼(用於支付回撥時)
    public String return_code;
    //微信h5支付時候的場景資訊官方的資訊模板 {"h5_info"://h5支付固定傳"h5_info" 
    //{"type":"",//場景型別 "wap_url":"",//WAP網站URL地址"wap_name": ""//WAP 網站名}}
    public String scene_info;
    public String getScene_info() {
        return scene_info;
    }
    public void setScene_info(String scene_info) {
        this.scene_info = scene_info;
    }
    public String getReturn_code() {
        return return_code;
    }
    public void setReturn_code(String return_code) {
        this.return_code = return_code;
    }
    public String getAppid() {
        return appid;
    }
    public String getMch_id() {
        return mch_id;
    }
    public String getDevice_info() {
        return device_info;
    }
    public String getNonce_str() {
        return nonce_str;
    }
    public String getSign() {
        return sign;
    }
    public String getBody() {
        return body;
    }
    public String getDetail() {
        return detail;
    }
    public String getAttach() {
        return attach;
    }
    public String getOut_trade_no() {
        return out_trade_no;
    }
    public String getFee_type() {
        return fee_type;
    }
    public int getTotal_fee() {
        return total_fee;
    }
    public String getSpbill_create_ip() {
        return spbill_create_ip;
    }
    public String getTime_start() {
        return time_start;
    }
    public String getTime_expire() {
        return time_expire;
    }
    public String getGoods_tag() {
        return goods_tag;
    }
    public String getNotify_url() {
        return notify_url;
    }
    public String getTrade_type() {
        return trade_type;
    }
    public String getProduct_id() {
        return product_id;
    }
    public String getLimit_pay() {
        return limit_pay;
    }
    public String getOpenid() {
        return openid;
    }
    public void setAppid(String appid) {
        this.appid = appid;
    }
    public void setMch_id(String mch_id) {
        this.mch_id = mch_id;
    }
    public void setDevice_info(String device_info) {
        this.device_info = device_info;
    }
    public void setNonce_str(String nonce_str) {
        this.nonce_str = nonce_str;
    }
    public void setSign(String sign) {
        this.sign = sign;
    }
    public void setBody(String body) {
        this.body = body;
    }
    public void setDetail(String detail) {
        this.detail = detail;
    }
    public void setAttach(String attach) {
        this.attach = attach;
    }
    public void setOut_trade_no(String out_trade_no) {
        this.out_trade_no = out_trade_no;
    }
    public void setFee_type(String fee_type) {
        this.fee_type = fee_type;
    }
    public void setTotal_fee(int total_fee) {
        this.total_fee = total_fee;
    }
    public void setSpbill_create_ip(String spbill_create_ip) {
        this.spbill_create_ip = spbill_create_ip;
    }
    public void setTime_start(String time_start) {
        this.time_start = time_start;
    }
    public void setTime_expire(String time_expire) {
        this.time_expire = time_expire;
    }
    public void setGoods_tag(String goods_tag) {
        this.goods_tag = goods_tag;
    }
    public void setNotify_url(String notify_url) {
        this.notify_url = notify_url;
    }
    public void setTrade_type(String trade_type) {
        this.trade_type = trade_type;
    }
    public void setProduct_id(String product_id) {
        this.product_id = product_id;
    }
    public void setLimit_pay(String limit_pay) {
        this.limit_pay = limit_pay;
    }
    public void setOpenid(String openid) {
        this.openid = openid;
    }
    public String getOut_refund_no() {
        return out_refund_no;
    }
    public void setOut_refund_no(String out_refund_no) {
        this.out_refund_no = out_refund_no;
    }
    public int getRefund_fee() {
        return refund_fee;
    }
    public void setRefund_fee(int refund_fee) {
        this.refund_fee = refund_fee;
    }
    public Integer getWeixinId() {
        return weixinId;
    }
    public void setWeixinId(Integer weixinId) {
        this.weixinId = weixinId;
    }
    public Member getMember() {
        return member;
    }
    public void setMember(Member member) {
        this.member = member;
    }
    public String getPrepayid() {
        return prepayid;
    }
    public void setPrepayid(String prepayid) {
        this.prepayid = prepayid;
    }
    public String getOp_user_id() {
        return op_user_id;
    }
    public void setOp_user_id(String op_user_id) {
        this.op_user_id = op_user_id;
    }


}

前端獲取ip:

<script src="http://pv.sohu.com/cityjson?ie=utf-8">
var ip = returnCitySN.cip;(有些手機不行,目前發現的是魅族和三星)判斷正不正確可以查自己的ip地址,微信獲取的是你所處網路的ip。也可以在java後臺獲取
</script>

在h5支付時出現的一些問題
這個
這個頁面就是你傳遞的ip和微信獲取的發起支付的ip不一致,這個時候你就要檢查獲取ip的程式碼
這裡寫圖片描述
出現這個情況的原因有兩個1.你的scene_info中的wap_url與你配置的授權域名不一致,需要去商戶平臺的產品中心->開發配置中修改 2. 配置的redirect_url與授權域名不一致,需要修改。
這個地方還有其他的一些錯誤,我沒遇到,以上兩個錯誤我遇到很多次,後來檢視官方文件才看見的(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_4)。