微信公衆號H5支付遇到的那些坑

簡史

官方文檔說的很清楚,商戶已有H5商城網站,用戶經過消息或掃描二維碼在微信內打開網頁時,能夠調用微信支付完成下單購買的流程。javascript

固然,最近微信支付平臺也加入了純H5支付,也就是說用戶能夠在微信之外的手機瀏覽器請求微信支付的場景喚起微信支付。html

固然,今天的主角是微信公衆號支付,其實也不必定非在公衆號中打開,只要在微信中打開就可使用。前端

實現

項目使用的springboot微服務來實現,如下都是簡單的僞代碼實現,具體邏輯見碼雲java

Main

其實就是一個初始化下單操做,前臺業務邏輯在這就不展現了,這個就是接收前臺參數的方法:jquery

@RequestMapping("/pay")
public String  pay(Product product,ModelMap map) {
    logger.info("H5支付(須要公衆號內支付)");
    String url =  weixinPayService.weixinPayMobile(product);
        return "redirect:"+url;
}

  

  

產品實體Bean:git

/**
 * 產品訂單信息
 * 建立者 科幫網
 * 建立時間    2017年7月27日
 */
@Data                
@NoArgsConstructor     
@AllArgsConstructor
public class Product implements Serializable {
    private static final long serialVersionUID = 1L;
    private String productId;// 商品ID
    private String subject;//訂單名稱 
    private String body;// 商品描述
    private String totalFee;// 總金額(單位是分)
    private String outTradeNo;// 訂單號(惟一)
    private String spbillCreateIp;// 發起人IP地址
    private String attach;// 附件數據主要用於商戶攜帶訂單的自定義數據
    private Short payType;// 支付類型(1:支付寶 2:微信 3:銀聯)
    private Short payWay;// 支付方式 (1:PC,平板 2:手機)
    private String frontUrl;// 前臺回調地址  非掃碼支付使用
}

  

  

因爲整合了Dubbo,使用PRC的方式調用,這裏定義一個service:web

@Override
public String weixinPayMobile(Product product) {
    StringBuffer url = new StringBuffer();
    String totalFee = product.getTotalFee();
    //redirect_uri 須要在微信支付端添加認證網址
    totalFee =  CommonUtil.subZeroAndDot(totalFee);
    url.append("http://open.weixin.qq.com/connect/oauth2/authorize?");
    url.append("appid="+ConfigUtil.APP_ID);
    url.append("&redirect_uri="+server_url+"weixinMobile/dopay?");
//注意 此處 get請求 拼接相關參數 用於redirect_uri獲取    url.append("outTradeNo="+product.getOutTradeNo()+"&totalFee="+totalFee);
    url.append("&response_type=code&scope=snsapi_base&state=");
    url.append("#wechat_redirect");
    return  url.toString();
}

  

 

Topay

你們有沒有注意到redirect_uri參數中,咱們定義了咱們本身系統中的url請求,以下:spring

@RequestMapping(value = "dopay")
    public String dopay(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //此處爲weixinPayMobile方法中拼接的參數
        String orderNo = request.getParameter("outTradeNo");
        String totalFee = request.getParameter("totalFee");
        //獲取code 這個在微信支付調用時會自動加上這個參數無須設置
        String code = request.getParameter("code");
        //獲取用戶openID(JSAPI支付必須傳openid)
        String openId = MobileUtil.getOpenId(code);
        String notify_url =server_url+"/weixinMobile/WXPayBack";//回調接口
        String trade_type = "JSAPI";// 交易類型H5支付
        SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
        ConfigUtil.commonParams(packageParams);
        packageParams.put("body","報告");// 商品描述
        packageParams.put("out_trade_no", orderNo);// 商戶訂單號
        packageParams.put("total_fee", totalFee);// 總金額
        packageParams.put("spbill_create_ip", AddressUtils.getIpAddr(request));// 發起人IP地址
        packageParams.put("notify_url", notify_url);// 回調地址
        packageParams.put("trade_type", trade_type);// 交易類型
        packageParams.put("openid", openId);//用戶openID
        String sign = PayCommonUtil.createSign("UTF-8", packageParams,ConfigUtil.API_KEY);
        packageParams.put("sign", sign);// 簽名
        String requestXML = PayCommonUtil.getRequestXml(packageParams);
        String resXml = HttpUtil.postData(ConfigUtil.UNIFIED_ORDER_URL, requestXML);
        Map map = XMLUtil.doXMLParse(resXml);
        String returnCode = (String) map.get("return_code");
        String returnMsg = (String) map.get("return_msg");
        StringBuffer url = new StringBuffer();
        if("SUCCESS".equals(returnCode)){
            String resultCode = (String) map.get("result_code");
            String errCodeDes = (String) map.get("err_code_des");
            if("SUCCESS".equals(resultCode)){
                //獲取預支付交易會話標識
                String prepay_id = (String) map.get("prepay_id");
                String prepay_id2 = "prepay_id=" + prepay_id;
                String packages = prepay_id2;
                SortedMap<Object, Object> finalpackage = new TreeMap<Object, Object>();
                String timestamp = DateUtil.getTimestamp();
                String nonceStr = packageParams.get("nonce_str").toString();
                finalpackage.put("appId",  ConfigUtil.APP_ID);
                finalpackage.put("timeStamp", timestamp);
                finalpackage.put("nonceStr", nonceStr);
                finalpackage.put("package", packages);  
                finalpackage.put("signType", "MD5");
                //這裏很重要  參數必定要正確 狗日的騰訊 參數到這裏就成大寫了
                //可能報錯信息(支付驗證簽名失敗 get_brand_wcpay_request:fail)
                sign = PayCommonUtil.createSign("UTF-8", finalpackage,ConfigUtil.API_KEY);
                url.append("redirect:/weixinMobile/payPage?");
                url.append("timeStamp="+timestamp+"&nonceStr=" + nonceStr + "&package=" + packages);
                url.append("&signType=MD5" + "&paySign=" + sign+"&appid="+ ConfigUtil.APP_ID);
                url.append("&orderNo="+orderNo+"&totalFee="+totalFee);
            }else{
                logger.info("訂單號:{}錯誤信息:{}",orderNo,errCodeDes);
                url.append("redirect:/weixinMobile/error?code=0&orderNo="+orderNo);//該訂單已支付
            }
        }else{
            logger.info("訂單號:{}錯誤信息:{}",orderNo,returnMsg);
            url.append("redirect:/weixinMobile/error?code=1&orderNo="+orderNo);//系統錯誤
        }
        return url.toString();
    }

  

  

其實,以上代碼就是一個認證(獲取openid)、下單的過程,最終獲取相關參數再重定向到pay頁面,也就是咱們定義的 redirect:/weixinMobile/payPage。api

//公衆號H5支付主頁
@RequestMapping(value = "payPage")
    public String pay(HttpServletRequest request, HttpServletResponse response) throws Exception {
    return "weixin/pay";
}

  

而後轉發到pay.jsp瀏覽器

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
//相關參數
String appId = request.getParameter("appid");
String timeStamp = request.getParameter("timeStamp");
String nonceStr = request.getParameter("nonceStr");
String packageValue = request.getParameter("package");
String paySign = request.getParameter("paySign");
String orderNo = request.getParameter("orderNo");
String totalFee  = request.getParameter("totalFee");
%>
<!DOCTYPE html>
<html>
<head>
<title>微信支付</title>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
<meta name="format-detection" content="telephone=no"/>
<script type="text/javascript" src="<%=basePath%>static/js/jquery-1.10.2.min.js"></script>
<!-- 引入 jweixin-1.0.0.js-->
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
</head>
<body>
        <article class="order-main ">
            <div class="ph_order">
                <div class=" affirm-info">
                    <h4 id="orderNo"></h4>
                    <h3 id="totalFee"></h3>
                    <div class="detail-dl">
                        <dl>
                            <dt>收款方</dt>
                            <dd>科幫網</dd>
                        </dl>
                        <dl>
                            <dt>商   品</dt>
                            <dd id="productName">充值幫幣</dd>
                        </dl>
                    </div>
                    <div  onclick="callpay()" class="pay-info">當即支付</div>
                </div>
            </div>
        </article>
</body>
<script type="text/javascript">
var orderNo = '<%=orderNo%>';
var totalFee  = '<%=totalFee%>';
$(function(){
    init();
});
function onBridgeReady(){
    WeixinJSBridge.invoke('getBrandWCPayRequest',{
             "appId" : "<%=appId%>",
             "timeStamp" : "<%=timeStamp%>",
             "nonceStr" : "<%=nonceStr%>", 
             "package" : "<%=packageValue%>",
             "signType" : "MD5",
             "paySign" : "<%=paySign%>" 
         },function(res){
             //使用以上方式判斷前端返回,微信團隊鄭重提示:res.err_msg將在用戶支付成功後返回    ok,但並不保證它絕對可靠。
             if(res.err_msg == "get_brand_wcpay_request:ok"){
                 window.location.href="http://前臺回調地址";
             }else if(res.err_msg == "get_brand_wcpay_request:cancel"){  
                 alert("用戶取消支付!");  
             }else if(res.err_msg == "get_brand_wcpay_request:fail"){  
                 alert("支付失敗!");  
             }  
    })
}
function callpay(){  
      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();
       }
}
function init(){
    $("#orderNo").html("科幫網-訂單編號:"+orderNo);
    totalFee = accDiv(totalFee,100);
    $("#totalFee").html("¥"+totalFee);
}
function accDiv(arg1,arg2){
    var t1=0,t2=0,r1,r2;
    try{t1=arg1.toString().split(".")[1].length;}catch(e){}
    try{t2=arg2.toString().split(".")[1].length;}catch(e){}
    with(Math){
        r1=Number(arg1.toString().replace(".",""));
        r2=Number(arg2.toString().replace(".",""));
        return (r1/r2)*pow(10,t2-t1);
    }
}
</script>
</html>

  

  

Notify

其實,這就是一個回調通知,用戶支付成功之後,微信會通知咱們後臺支付狀態,而後咱們根據訂單信息完成下一步業務邏輯。

@RequestMapping(value = "WXPayBack")
    public void WXPayBack(HttpServletRequest request, HttpServletResponse response){
        String resXml = "";
        try {
            //解析XML
            Map<String, String> map = MobileUtil.parseXml(request);
            String return_code = map.get("return_code");//狀態
            String out_trade_no = map.get("out_trade_no");//訂單號
            if (return_code.equals("SUCCESS")) {
                if (out_trade_no != null) {
                    //處理訂單邏輯
                    logger.info("微信手機支付回調成功訂單號:{}",out_trade_no);
                    resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
                }
            }else{
                logger.info("微信手機支付回調失敗訂單號:{}",out_trade_no);
                resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文爲空]]></return_msg>" + "</xml> ";
            }
        } catch (Exception e) {
            logger.error("手機支付回調通知失敗",e);
             resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文爲空]]></return_msg>" + "</xml> ";
        }
        try {
            // ------------------------------
            // 處理業務完畢
            // ------------------------------
            BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
            out.write(resXml.getBytes());
            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

  

其實,當你完成集成測試的那一刻,也就沒啥子坑了,相關的注意事項都在代碼中有體現。

詳細代碼見:碼雲

相關文章
相關標籤/搜索