一. 案例介绍
这里模拟一个实际业务场景,进行介绍微信支付,业务功能包括:登录、注册、充值、查看充值记录。
页面图:
二. 概要设计
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,需要事先获取