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

来源:http://www.cnblogs.com/smallSevens/archive/2017/08/21/7406746.html
-Advertisement-
Play Games

簡史 官方文檔說的很清楚,商戶已有H5商城網站,用戶通過消息或掃描二維碼在微信內打開網頁時,可以調用微信支付完成下單購買的流程。 當然,最近微信支付平臺也加入了純H5支付,也就是說用戶可以在微信以外的手機瀏覽器請求微信支付的場景喚起微信支付。 當然,今天的主角是微信公眾號支付,其實也不一定非在公眾號 ...


簡史

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

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

當然,今天的主角是微信公眾號支付,其實也不一定非在公眾號中打開,只要在微信中打開就可以使用。

實現

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

Main

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

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

  

  

產品實體Bean:

/**
 * 產品訂單信息
 * 創建者 科幫網
 * 創建時間    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:

@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請求,如下:

@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。

//公眾號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();
        }
    }

  

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

詳細代碼見:碼雲


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、SpringMVC概述 1.1 SpringMVC簡介 SpringMVC也叫Spring Web MVC,屬於表現層框架。SpringMVC是Spring框架的一部分,是在Spring3.0後發佈的。 1.2 第一個SpringMVC程式 需求:用戶提交一個請求,服務端處理器在接受到這個請求後 ...
  • 本節繼續探討Java 8中的函數式數據處理 - Stream API,主要討論各種強大方便的收集器,它們都有什麼用?如何使用?基本實現原理是什麼呢? ...
  • 1、查找字元位置函數: 2、提取子字元函數(雙位元組) 3、替換字元串 4、查詢字元串長度 5、比較字元函數 6、分割成數組函數 7、去除空格: 8、加空格函數 9、返回指定的字元或ascii 10、HTML代碼有關函數 11、字元大小寫轉換函數 12、資料庫相關函數 13、連接函數 ...
  • PHP內置的mail函數使用起來不夠方便,另外受其他語言的影響,博主更偏好面向對象的包管理模式,因此phpmailer成為了我用PHP發送郵件的首選,這裡分享給大家。 ...
  • 網上關於pexpect的介紹基本都類似於這樣http://blog.csdn.net/sdustliyang/article/details/23373485,但是並沒有關於下述問題的解釋 問題:可以ssh到主機,但是後面執行的命令無法生效 代碼如下: 可以看到ssh是成功連接的,但是ls /hom ...
  • Struts2.5 struts是開源框架。使用Struts的目的是為了幫助我們減少在運用MVC設計模型來開發Web應用的時間。如果我們想混合使用Servlets和JSP的優點來建立可擴展的應用,struts是一個不錯的選擇。 Struts 是Apache軟體基金會(ASF)贊助的一個開源項目。它最 ...
  • 上面的代碼實現的只是簡單而繁瑣的一種,後面的代碼會一直重覆,因此並沒有寫下去,後面的是我看視頻附帶的代碼,我也附上,較我寫的完整一些 ...
  • 1 1、請求周期 2 url> 路由 > 函數或類 > 返回字元串或者模板語言? 3 4 Form表單提交: 5 提交 -> url > 函數或類中的方法 6 - .... 7 HttpResponse('....') 8 render(request,'index.html') 9 redirec ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...