一. 案例介紹
這里模擬一個實際業務場景,進行介紹微信支付,業務功能包括:登錄、注冊、充值、查看充值記錄。
頁面圖:
二. 概要設計
1.數據庫設計
這里數據庫包括兩張表:用戶表和訂單表。
用戶表: 主鍵id、用戶名、密碼、openid、注冊時間
訂單表: 主鍵id、用戶id,商品名稱、訂單狀態(0代表下單了未支付,1代表支付成功)、商品價錢、下單時間
2.微信支付流程
這里結合該案例,來說明微信支付流程。
該流程中涉及到4種角色,分別是微信用戶、微信客戶端、商戶系統(自己的系統)、微信支付系統。
流程1:
①用戶登錄微信客戶端系統→②進入主頁→③去支付→④生成商戶系統訂單→⑤調用微信統一下單API,在微信支付系統里生成預支付訂單,並返回預支付訂單信息→
⑥商戶系統拿到返回的預支付訂單信息,進行簽名,便按照一定的格式返給微信客戶端(JSAPI頁面)→⑦微信客戶端JSAPI頁面拿到參數,請求支付,輸入密碼,進行支付→
⑧這時會進行2個並行處理→異步通知商戶支付結果,商戶系統接到通知后,需要修改訂單的業務邏輯(該案例修改訂單狀態0改為1),商戶系統需要告知微信系統處理結果
→給微信客戶端發送支付結果,並發微信消息提示
⑨微信客戶端跳轉到商戶H5頁面,查詢商戶后台支付結果
⑩ 這時候分兩種情況
A. 商戶后台系統,已經接到通知,進行了業務修改,直接返回成功。
B. 商戶后台系統,沒有接到通知,這時去查詢微信支付系統,如果微信支付系統成功,說明確實付款成功,只是因為網絡延遲造成商戶后台暫時沒有接到通知,如果查詢后發現未付款成功,則返回付款失敗。

3.代碼配置
(1).參數配置
(2).前端頁面代碼
1 $(function () { 2 // 當微信內置瀏覽器完成內部初始化后會觸發WeixinJSBridgeReady事件。 3 document.addEventListener('WeixinJSBridgeReady', function onBridgeReady() { 4 //公眾號支付 5 document.getElementById("pay").onclick = function () { 6 //1.前端驗證 7 var money = $('#num').val(); 8 if (money == "") { 9 alert('請將信息輸入完整'); 10 return; 11 } 12 mui('#pay').button('loading'); 13 //2.進行下單 14 $.ajax({ 15 type: 'POST', 16 url: '/WeiXinGz/GetAPI', 17 data: { "money": money }, 18 cache: false, 19 dataType: 'json', 20 success: function (jsonData) { 21 if (jsonData.status == "1") { 22 //公眾號支付 23 WeixinJSBridge.invoke('getBrandWCPayRequest', jsonData.payData, function (res) { 24 // 使用以下方式判斷前端返回,微信團隊鄭重提示:res.err_msg將在用戶支付成功后返回ok,但並不保證它絕對可靠。 25 //【因此微信團隊建議:】當收到ok返回時,向商戶后台詢問是否收到交易成功的通知, 26 //若收到通知,前端展示交易成功的界面; 27 //若此時未收到通知,商戶后台主動調用查詢訂單接口,查詢訂單的當前狀態,並反饋給前端展示相應的界面。 28 if (res.err_msg == "get_brand_wcpay_request:ok") { 29 //JS API的返回結果get_brand_wcpay_request:ok僅在用戶成功完成支付時返回 30 $.ajax({ 31 type: 'POST', 32 url: '/WeiXinGz/QueryOrder', 33 data: { 34 orderId: jsonData.orderId 35 }, 36 cache: false, 37 dataType: 'text', 38 success: function (jsonData) { 39 if (jsonData == "ok") { 40 alert("支付成功", "提示", function () { 41 alert("頁面跳轉等業務處理"); 42 }); 43 mui('#pay').button('reset'); 44 } else { 45 alert("支付失敗,請稍后重試!如果收到支付通知,切勿重復支付1!"); 46 mui('#pay').button('reset'); 47 } 48 }, 49 error: function (XMLHttpRequest, textStatus, errorThrown) { 50 alert("支付失敗,請稍后重試!如果收到支付通知,切勿重復支付2!"); 51 mui('#pay').button('reset'); 52 } 53 }); 54 } else if (res.err_msg == "get_brand_wcpay_request:cancel") { 55 //由於前端交互復雜,get_brand_wcpay_request:cancel或者get_brand_wcpay_request:fail可以統一處理為用戶遇到錯誤或者主動放棄,不必細化區分。 56 alert("您放棄了支付"); 57 mui('#pay').button('reset'); 58 } else { 59 //由於前端交互復雜,get_brand_wcpay_request:cancel或者get_brand_wcpay_request:fail可以統一處理為用戶遇到錯誤或者主動放棄,不必細化區分。 60 alert("支付失敗,請稍后重試!如果收到支付通知,切勿重復支付3!"); 61 mui('#pay').button('reset'); 62 } 63 }); 64 } else { 65 alert(jsonData.promptInfor); 66 mui('#pay').button('reset'); 67 } 68 }, 69 error: function (XMLHttpRequest, textStatus, errorThrown) { 70 alert("微信訂單提交失敗,請稍后重試4!"); 71 mui('#pay').button('reset'); 72 } 73 }); 74 75 } 76 }, false); 77 });
(3).統一下單接口
1 /// <summary> 2 /// 統一下單接口 3 /// </summary> 4 /// <param name="money">錢數</param> 5 /// <returns></returns> 6 public ActionResult GetAPI(string money) 7 { 8 try 9 { 10 //一.系統本身自有的業務處理 11 //1.必要信息的初始化 12 string userId = Session["userId"].ToString(); //用戶主鍵 13 UserInfor userInfor = db.Set<UserInfor>().Where(a => a.id == userId).FirstOrDefault(); 14 string orderId = GenerateOrderNum(); //生成訂單號 15 string totalFee = money;//設置默認商品費用為【1分】 16 string nonceStr = TenPayV3Util.GetNoncestr(); //獲取 隨機字符串 17 string openid = userInfor.openId; 18 //2.自己商戶系統下單 19 OrderInfor orderInfor = new OrderInfor(); 20 orderInfor.id = orderId; 21 orderInfor.uid = userInfor.id; 22 orderInfor.goodName = "測試商品"; 23 orderInfor.goodPrice = totalFee; 24 orderInfor.addTime = DateTime.Now; 25 orderInfor.status = "0"; //已經下單,但未付款 26 db.Set<OrderInfor>().Add(orderInfor); 27 db.SaveChanges(); 28 29 30 //二.微信系統下單 31 //1.創建支付應答對象並初始化 32 RequestHandler packageReqHandler = new RequestHandler(null); 33 packageReqHandler.Init(); 34 //1.1設置統一下單的參數 35 packageReqHandler.SetParameter("appid", ConfigHelp.AppSettings("AppId")); //公眾賬號ID 36 packageReqHandler.SetParameter("mch_id", ConfigHelp.AppSettings("MchId")); //商戶號 37 packageReqHandler.SetParameter("nonce_str", nonceStr); //隨機字符串 38 packageReqHandler.SetParameter("body", "科技-服務"); //商品描述 39 packageReqHandler.SetParameter("out_trade_no", orderId); //商戶訂單號 40 packageReqHandler.SetParameter("total_fee", totalFee); //商品金額,以分為單位 41 packageReqHandler.SetParameter("spbill_create_ip", Request.UserHostAddress); //終端IP 42 packageReqHandler.SetParameter("notify_url", ConfigHelp.AppSettings("notify_url")); //微信支付異步通知回調地址 43 packageReqHandler.SetParameter("trade_type", "JSAPI"); //交易類型 代表公眾號支付 44 packageReqHandler.SetParameter("openid", openid); //用戶標識 45 string sign = packageReqHandler.CreateMd5Sign("key", ConfigHelp.AppSettings("key")); //預支付簽名 46 packageReqHandler.SetParameter("sign", sign); 47 //1.2 下單數據格式轉換 48 string data = packageReqHandler.ParseXML(); 49 //1.3 進行下單 50 string result = TenPayV3.Unifiedorder(data); 51 //2.對下單返回結果進行分析 52 XDocument res = XDocument.Parse(result); 53 //2.1 對返回結果進行判斷 54 55 //2.2 成功的情況下獲取必要的參數 56 string prepayId = res.Element("xml").Element("prepay_id").Value; //獲取預支付訂單編號prepayId 57 //3. 獲取支付參數並簽名 58 string timeStamp = TenPayV3Util.GetTimestamp(); //獲取時間戳 59 //設置支付參數 60 RequestHandler paySignReqHandler = new RequestHandler(null); 61 paySignReqHandler.SetParameter("appId", ConfigHelp.AppSettings("AppId")); 62 paySignReqHandler.SetParameter("timeStamp", TenPayV3Util.GetTimestamp()); 63 paySignReqHandler.SetParameter("nonceStr", nonceStr); 64 paySignReqHandler.SetParameter("package", string.Format("prepay_id={0}", prepayId)); 65 paySignReqHandler.SetParameter("signType", "MD5"); //簽名【MD5】 66 string paySign = paySignReqHandler.CreateMd5Sign("key", ConfigHelp.AppSettings("key")); //JSAPI支付簽名 67 var payData = new 68 { 69 appId = ConfigHelp.AppSettings("AppId"), 70 timeStamp = timeStamp, 71 nonceStr = nonceStr, 72 package = string.Format("prepay_id={0}", prepayId), 73 signType = "MD5", 74 paySign = paySign, 75 }; 76 return Json(new 77 { 78 status = "1", 79 promptInfor = "微信下單成功", 80 payData = payData, 81 orderId = orderId 82 }); 83 } 84 catch (Exception ex) 85 { 86 string a = ex.Message; 87 88 return Json(new 89 { 90 status = "0", 91 promptInfor = a 92 }); 93 } 94 }
(4).微信異步通知接口
1 public ActionResult PayNotifyUrl() 2 { 3 //獲取當前http請求 4 HttpContext httpContext = System.Web.HttpContext.Current; 5 ResponseHandler notifyDataHandler = new ResponseHandler(httpContext); 6 //返回狀態碼【SUCCESS/FAIL】此字段是通信標識 7 string return_code = notifyDataHandler.GetParameter("return_code"); 8 //返回信息【如非空,為錯誤原因】 9 string return_msg = notifyDataHandler.GetParameter("return_msg"); 10 //表示通信成功 11 if (return_code == "SUCCESS") 12 { 13 //獲取業務結果【交易是否成功(SUCCESS/FAIL)】 14 string result_code = notifyDataHandler.GetParameter("result_code"); 15 //表示業務結果成功 16 if (result_code == "SUCCESS") 17 { 18 //設置簽名密鑰 19 notifyDataHandler.SetKey(ConfigHelp.AppSettings("key")); 20 //驗證請求是否從微信發過來(安全)【驗證簽名】 21 if (notifyDataHandler.IsTenpaySign()) 22 { 23 //獲取訂單編號 24 string out_trade_no = notifyDataHandler.GetParameter("out_trade_no"); 25 //檢查是否返回商戶訂單號 26 if (!string.IsNullOrEmpty(out_trade_no)) 27 { 28 try 29 { 30 OrderInfor orderInfor = db.Set<OrderInfor>().Where(a => a.id == out_trade_no).FirstOrDefault(); 31 if (orderInfor != null) 32 { 33 //這里需要根據訂單號更改系統的業務 34 //這里模擬測試記錄訂單號 35 orderInfor.status = "1"; //表示付款成功,成功回調 36 db.Entry(orderInfor).State = EntityState.Modified; 37 db.SaveChanges(); 38 39 return_code = "SUCCESS"; 40 return_msg = "OK"; 41 } 42 else 43 { 44 return_code = "FAIL"; 45 return_msg = "商戶系統中不存在該訂單"; 46 } 47 } 48 catch (Exception ex) 49 { 50 //系統異常 51 return_code = "FAIL"; 52 return_msg = ex.Message; 53 } 54 } 55 else 56 { 57 //支付結果中商戶訂單號不存在 58 return_code = "FAIL"; 59 return_msg = "支付結果中商戶訂單號不存在"; 60 } 61 } 62 else 63 { 64 //簽名失敗 65 return_code = "FAIL"; 66 return_msg = "簽名失敗"; 67 } 68 } 69 else 70 { 71 //交易失敗 72 return_code = "FAIL"; 73 return_msg = "交易失敗"; 74 } 75 } 76 //商戶處理后同步返回給微信參數 77 string xml = string.Format(@"<xml><return_code><![CDATA[{0}]]></return_code><return_msg><![CDATA[{1}]]></return_msg></xml>", return_code, return_msg); 78 //返回處理結果 79 return Content(xml, "text/xml"); 80 }
(5).JSAPI接口請求
1 //公眾號支付 2 WeixinJSBridge.invoke('getBrandWCPayRequest', jsonData.payData, function (res) { 3 // 使用以下方式判斷前端返回,微信團隊鄭重提示:res.err_msg將在用戶支付成功后返回ok,但並不保證它絕對可靠。 4 //【因此微信團隊建議:】當收到ok返回時,向商戶后台詢問是否收到交易成功的通知, 5 //若收到通知,前端展示交易成功的界面; 6 //若此時未收到通知,商戶后台主動調用查詢訂單接口,查詢訂單的當前狀態,並反饋給前端展示相應的界面。 7 if (res.err_msg == "get_brand_wcpay_request:ok") { 8 //JS API的返回結果get_brand_wcpay_request:ok僅在用戶成功完成支付時返回 9 $.ajax({ 10 type: 'POST', 11 url: '/WeiXinGz/QueryOrder', 12 data: { 13 orderId: jsonData.orderId 14 }, 15 cache: false, 16 dataType: 'text', 17 success: function (jsonData) { 18 if (jsonData == "ok") { 19 alert("支付成功", "提示", function () { 20 alert("頁面跳轉等業務處理"); 21 }); 22 mui('#pay').button('reset'); 23 } else { 24 alert("支付失敗,請稍后重試!如果收到支付通知,切勿重復支付1!"); 25 mui('#pay').button('reset'); 26 } 27 }, 28 error: function (XMLHttpRequest, textStatus, errorThrown) { 29 alert("支付失敗,請稍后重試!如果收到支付通知,切勿重復支付2!"); 30 mui('#pay').button('reset'); 31 } 32 }); 33 } else if (res.err_msg == "get_brand_wcpay_request:cancel") { 34 //由於前端交互復雜,get_brand_wcpay_request:cancel或者get_brand_wcpay_request:fail可以統一處理為用戶遇到錯誤或者主動放棄,不必細化區分。 35 alert("您放棄了支付"); 36 mui('#pay').button('reset'); 37 } else { 38 //由於前端交互復雜,get_brand_wcpay_request:cancel或者get_brand_wcpay_request:fail可以統一處理為用戶遇到錯誤或者主動放棄,不必細化區分。 39 alert("支付失敗,請稍后重試!如果收到支付通知,切勿重復支付3!"); 40 mui('#pay').button('reset'); 41 } 42 });

1 /// <summary> 2 /// 微信訂單查詢接口 3 /// </summary> 4 /// <param name="orderId">訂單編號id</param> 5 /// <returns></returns> 6 //[WeixinInternalRequest("無法訪問!")] 7 public ActionResult QueryOrder(string orderId) 8 { 9 try 10 { 11 //一.先查商戶后台的訂單狀態,判斷微信端是否異步通知商戶后台了!!! 12 OrderInfor orderInfor = db.Set<OrderInfor>().Where(a => a.id == orderId).FirstOrDefault(); 13 //判斷訂單狀態 14 if (orderInfor.status == "1") 15 { 16 //表示查詢成功 17 return Content("ok"); 18 } 19 else if (orderInfor == null || orderInfor.status != "1") 20 { 21 //二.進行調用下面的微信查詢api進行查詢 22 //生成隨機字符串 23 string nonceStr = TenPayV3Util.GetNoncestr(); 24 RequestHandler packageReqHandler = new RequestHandler(null); 25 //設置package訂單參數 26 packageReqHandler.SetParameter("appid", ConfigHelp.AppSettings("AppId")); //公眾賬號ID 27 packageReqHandler.SetParameter("mch_id", ConfigHelp.AppSettings("MchId")); //商戶號 28 packageReqHandler.SetParameter("out_trade_no", orderId); //填入商家訂單號 29 packageReqHandler.SetParameter("nonce_str", nonceStr); //隨機字符串 30 string sign = packageReqHandler.CreateMd5Sign("key", ConfigHelp.AppSettings("key"));//參數進行簽名 31 packageReqHandler.SetParameter("sign", sign); //參數中添加簽名字符串 32 string data = packageReqHandler.ParseXML(); //將傳的參數轉化為XML格式字符串 33 var result = TenPayV3.OrderQuery(data); //調用訂單查詢接口 34 var res = XDocument.Parse(result); 35 //返回狀態碼【SUCCESS/FAIL】此字段是通信標識 36 string return_code = res.Element("xml").Element("return_code").Value; 37 if (return_code == "SUCCESS") 38 { 39 //獲取業務結果【交易是否成功(SUCCESS/FAIL)】 40 string result_code = res.Element("xml").Element("result_code").Value; 41 if (result_code == "SUCCESS") 42 { 43 //交易狀態 44 /**SUCCESS—支付成功 45 *REFUND—轉入退款 46 *NOTPAY—未支付 47 *CLOSED—已關閉 48 *REVOKED—已撤銷(刷卡支付) 49 *USERPAYING--用戶支付中 50 *PAYERROR--支付失敗(其他原因,如銀行返回失敗) 51 */ 52 string trade_state = res.Element("xml").Element("trade_state").Value; 53 if (return_code == "SUCCESS") 54 { 55 return Content("ok"); 56 } 57 } 58 } 59 60 } 61 //未查詢到該訂單或者該訂單交易狀態不相符 62 return Content("error"); 63 } 64 catch (Exception ex) 65 { 66 //拋異常信息,返回異常消息 67 return Content(ex.Message); 68 } 69 }
上述代碼中,用到的openid,需要事先獲取