【微信开发】---- 公众号支付
公众号支付就是在微信里面的H5页面唤起微信支付,不用扫码即可付款的功能。做这个功能首先要明确的就是,只有和商户号mch_id匹配的appid才能成功支付。商户号在注册成功的时候就会将相关信息发送到邮箱里面。而唤起支付的一个关键是靠openid拿到统一下单。而openid是和appid一一对应的。也就是说如果你登录使用的appid不是公众号的appid,得到的openid就无法唤起公众号内的支付(会出现appid和商户号不匹配的错误)。曾经就在这个地方绕了个弯,因为微信的开放平台可以创建网站应用,也有一个appid和appsecreat,也可以在微信里面一键登录。
业务流程
下面是微信的官方流程,看似有点复杂,重点就是要拿到统一下单接口返回的json串,其他按照官方demo基本就能正确,下面说一下几个细节。
创建订单
在调用微信公众号支付之前,首先我们自己要把订单创建好。比如一个充值的订单。主要是先确定下金额再进行下一步。
public JsonResult CreateRecharegOrder(decimal money) { if (money < (decimal)0.01) return Json(new PaymentResult("充值金额非法!")); var user = _workContext.CurrentUser; var order = _paymentService.CreateRechargeOrder(user.Id, money); return Json(new PaymentResult(true) {OrderId = order.OrderNumber}); }
调用统一下单
订单创建成功之后,页面跳转到支付页面,这个时候就是按照官方的流程去拿prepay_id和paySign,微信的demo中提供了一个jsApiPay的对象。但这个对象需要一个page对象初始化。
[LoginValid] public ActionResult H5Pay(string orderNumber) { var user = _workContext.CurrentUser; var order = _paymentService.GetOrderByOrderNumber(orderNumber); //判断订单是否存在 //订单是否已经支付了 var openid = user.OpenId; var jsApipay = new JsApiPayMvc(this.ControllerContext.HttpContext); jsApipay.openid = openid; jsApipay.total_fee = (int)order.Amount * 100; WxPayData unifiedOrderResult = jsApipay.GetUnifiedOrderResult(); ViewBag.wxJsApiParam = jsApipay.GetJsApiParameters();//获取H5调起JS API参数 ViewBag.unifiedOrder = unifiedOrderResult.ToPrintStr(); ViewBag.OrderNumber = order.OrderNumber; return View(); }
在MVC中我们简单改一下就可以了。也就是把page对象换成httpContext即可。然后里面的方法就可以直接用了。
JsApiPayMvc:
using System; using System.Collections.Generic; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Runtime.Serialization; using System.IO; using System.Text; using System.Net; using System.Web.Security; using LitJson; namespace WxPayAPI { public class JsApiPayMvc { /// <summary> /// 保存页面对象,因为要在类的方法中使用Page的Request对象 /// </summary> public HttpContextBase context { get; set; } /// <summary> /// openid用于调用统一下单接口 /// </summary> public string openid { get; set; } /// <summary> /// access_token用于获取收货地址js函数入口参数 /// </summary> public string access_token { get; set; } /// <summary> /// 商品金额,用于统一下单 /// </summary> public int total_fee { get; set; } /// <summary> /// 统一下单接口返回结果 /// </summary> public WxPayData unifiedOrderResult { get; set; } public JsApiPayMvc(HttpContextBase _context) { context = _context; } /** * * 网页授权获取用户基本信息的全部过程 * 详情请参看网页授权获取用户基本信息:http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html * 第一步:利用url跳转获取code * 第二步:利用code去获取openid和access_token * */ public void GetOpenidAndAccessToken(string code) { if (!string.IsNullOrEmpty(code)) { //获取code码,以获取openid和access_token Log.Debug(this.GetType().ToString(), "Get code : " + code); GetOpenidAndAccessTokenFromCode(code); } else { //构造网页授权获取code的URL string host = context.Request.Url.Host; string path = context.Request.Path; string redirect_uri = HttpUtility.UrlEncode("http://" + host + path); WxPayData data = new WxPayData(); data.SetValue("appid", WxPayConfig.APPID); data.SetValue("redirect_uri", redirect_uri); data.SetValue("response_type", "code"); data.SetValue("scope", "snsapi_base"); data.SetValue("state", "STATE" + "#wechat_redirect"); string url = "https://open.weixin.qq.com/connect/oauth2/authorize?" + data.ToUrl(); Log.Debug(this.GetType().ToString(), "Will Redirect to URL : " + url); try { //触发微信返回code码 context.Response.Redirect(url);//Redirect函数会抛出ThreadAbortException异常,不用处理这个异常 } catch(System.Threading.ThreadAbortException ex) { } } } /** * * 通过code换取网页授权access_token和openid的返回数据,正确时返回的JSON数据包如下: * { * "access_token":"ACCESS_TOKEN", * "expires_in":7200, * "refresh_token":"REFRESH_TOKEN", * "openid":"OPENID", * "scope":"SCOPE", * "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL" * } * 其中access_token可用于获取共享收货地址 * openid是微信支付jsapi支付接口统一下单时必须的参数 * 更详细的说明请参考网页授权获取用户基本信息:http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html * @失败时抛异常WxPayException */ public void GetOpenidAndAccessTokenFromCode(string code) { try { //构造获取openid及access_token的url WxPayData data = new WxPayData(); data.SetValue("appid", WxPayConfig.APPID); data.SetValue("secret", WxPayConfig.APPSECRET); data.SetValue("code", code); data.SetValue("grant_type", "authorization_code"); string url = "https://api.weixin.qq.com/sns/oauth2/access_token?" + data.ToUrl(); //请求url以获取数据 string result = HttpService.Get(url); Log.Debug(this.GetType().ToString(), "GetOpenidAndAccessTokenFromCode response : " + result); //保存access_token,用于收货地址获取 JsonData jd = JsonMapper.ToObject(result); access_token = (string)jd["access_token"]; //获取用户openid openid = (string)jd["openid"]; Log.Debug(this.GetType().ToString(), "Get openid : " + openid); Log.Debug(this.GetType().ToString(), "Get access_token : " + access_token); } catch (Exception ex) { Log.Error(this.GetType().ToString(), ex.ToString()); throw new WxPayException(ex.ToString()); } } /** * 调用统一下单,获得下单结果 * @return 统一下单结果 * @失败时抛异常WxPayException */ public WxPayData GetUnifiedOrderResult() { //统一下单 WxPayData data = new WxPayData(); data.SetValue("body", "test"); data.SetValue("attach", "test"); data.SetValue("out_trade_no", WxPayApi.GenerateOutTradeNo()); data.SetValue("total_fee", total_fee); data.SetValue("time_start", DateTime.Now.ToString("yyyyMMddHHmmss")); data.SetValue("time_expire", DateTime.Now.AddMinutes(10).ToString("yyyyMMddHHmmss")); data.SetValue("goods_tag", "test"); data.SetValue("trade_type", "JSAPI"); data.SetValue("openid", openid); WxPayData result = WxPayApi.UnifiedOrder(data); if (!result.IsSet("appid") || !result.IsSet("prepay_id") || result.GetValue("prepay_id").ToString() == "") { Log.Error(this.GetType().ToString(), "UnifiedOrder response error!"); throw new WxPayException("UnifiedOrder response error!"); } unifiedOrderResult = result; return result; } /** * * 从统一下单成功返回的数据中获取微信浏览器调起jsapi支付所需的参数, * 微信浏览器调起JSAPI时的输入参数格式如下: * { * "appId" : "wx2421b1c4370ec43b", //公众号名称,由商户传入 * "timeStamp":" 1395712654", //时间戳,自1970年以来的秒数 * "nonceStr" : "e61463f8efa94090b1f366cccfbbb444", //随机串 * "package" : "prepay_id=u802345jgfjsdfgsdg888", * "signType" : "MD5", //微信签名方式: * "paySign" : "70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名 * } * @return string 微信浏览器调起JSAPI时的输入参数,json格式可以直接做参数用 * 更详细的说明请参考网页端调起支付API:http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7 * */ public string GetJsApiParameters() { Log.Debug(this.GetType().ToString(), "JsApiPay::GetJsApiParam is processing..."); WxPayData jsApiParam = new WxPayData(); jsApiParam.SetValue("appId", unifiedOrderResult.GetValue("appid")); jsApiParam.SetValue("timeStamp", WxPayApi.GenerateTimeStamp()); jsApiParam.SetValue("nonceStr", WxPayApi.GenerateNonceStr()); jsApiParam.SetValue("package", "prepay_id=" + unifiedOrderResult.GetValue("prepay_id")); jsApiParam.SetValue("signType", "MD5"); jsApiParam.SetValue("paySign", jsApiParam.MakeSign()); string parameters = jsApiParam.ToJson(); Log.Debug(this.GetType().ToString(), "Get jsApiParam : " + parameters); return parameters; } /** * * 获取收货地址js函数入口参数,详情请参考收货地址共享接口:http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_9 * @return string 共享收货地址js函数需要的参数,json格式可以直接做参数使用 */ public string GetEditAddressParameters() { string parameter = ""; try { string host = context.Request.Url.Host; string path = context.Request.Path; string queryString = context.Request.Url.Query; //这个地方要注意,参与签名的是网页授权获取用户信息时微信后台回传的完整url string url = "http://" + host + path + queryString; //构造需要用SHA1算法加密的数据 WxPayData signData = new WxPayData(); signData.SetValue("appid",WxPayConfig.APPID); signData.SetValue("url", url); signData.SetValue("timestamp",WxPayApi.GenerateTimeStamp()); signData.SetValue("noncestr",WxPayApi.GenerateNonceStr()); signData.SetValue("accesstoken",access_token); string param = signData.ToUrl(); Log.Debug(this.GetType().ToString(), "SHA1 encrypt param : " + param); //SHA1加密 string addrSign = FormsAuthentication.HashPasswordForStoringInConfigFile(param, "SHA1"); Log.Debug(this.GetType().ToString(), "SHA1 encrypt result : " + addrSign); //获取收货地址js函数入口参数 WxPayData afterData = new WxPayData(); afterData.SetValue("appId",WxPayConfig.APPID); afterData.SetValue("scope","jsapi_address"); afterData.SetValue("signType","sha1"); afterData.SetValue("addrSign",addrSign); afterData.SetValue("timeStamp",signData.GetValue("timestamp")); afterData.SetValue("nonceStr",signData.GetValue("noncestr")); //转为json格式 parameter = afterData.ToJson(); Log.Debug(this.GetType().ToString(), "Get EditAddressParam : " + parameter); } catch (Exception ex) { Log.Error(this.GetType().ToString(), ex.ToString()); throw new WxPayException(ex.ToString()); } return parameter; } } }
View Code
这个页面可以在本地调试,可以比较方便的确认参数是否ok。
唤起支付
官方页面的示例如下:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6 但主要的参数(mark部分)是由后台生成的,也就是上一个步骤的ViewBag.wxJsApiParam
function onBridgeReady(){ WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId" : "wx2421b1c4370ec43b", //公众号名称,由商户传入 "timeStamp":" 1395712654", //时间戳,自1970年以来的秒数 "nonceStr" : "e61463f8efa94090b1f366cccfbbb444", //随机串 "package" : "prepay_id=u802345jgfjsdfgsdg888", "signType" : "MD5", //微信签名方式: "paySign" : "70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名 }, function(res){ if(res.err_msg == "get_brand_wcpay_request:ok" ) {} // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。 } ); }
所以在MVC中要这样写:
@{ ViewBag.Title = "微信支付"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div class="page" id="Wxpayment"> <div class="content"> <div>订单详情:@Html.Raw(ViewBag.unifiedOrder)</div> <button id="h5pay" onclick="callpay()">支付</button> </div> <input type="hidden" value="@ViewBag.OrderNumber" id="ordernum"/> </div> <script type="text/javascript"> //调用微信JS api 支付 function jsApiCall() { WeixinJSBridge.invoke( 'getBrandWCPayRequest', @Html.Raw(ViewBag.wxJsApiParam),//josn串 function (res) { WeixinJSBridge.log(res.err_msg); //alert(res.err_code + res.err_desc + res.err_msg); if (res.err_msg == "get_brand_wcpay_request:ok") { var num = $("#ordernum").val(); $.post("/payment/WeiXinPaySuccess", { ordernumber: num }, function(data) { if (data.IsSuccess === true) { alert("支付成功"); location.href = document.referrer; } else { } }); } if (res.err_msg == 'get_brand_wcpay_request:cancel') { $('.button').removeAttr('submitting'); alert('取消支付'); } } ); } function callpay() { if (typeof WeixinJSBridge == "undefined") { alert("WeixinJSBridge ="); if (document.addEventListener) { document.addEventListener('WeixinJSBridgeReady', jsApiCall, false); } else if (document.attachEvent) { document.attachEvent('WeixinJSBridgeReady', jsApiCall); document.attachEvent('onWeixinJSBridgeReady', jsApiCall); } } else { jsApiCall(); } } </script>
必须要用Html.Raw,不然json解析不对,无法支付。这个时候点击页面,会出现微信的加载效果,但别高兴的太早,还是会出错,出现一个“3当前的URL未注册”
原因就在于,需要在公众号中设置支付目录。而这个支付目录是大小写敏感的,所以你得多试几次。直到弹出输入密码的窗口才是真的流程正确了。然后支付成功之后马上就可以收到js中的回调,这个时候你可以去处理你的订单和业务逻辑。
官方文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3
微信登录:http://www.cnblogs.com/stoneniqiu/p/5380606.html
微信扫码支付及MVC demo:http://www.cnblogs.com/stoneniqiu/p/5525548.html
官方demo:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1
【微信开发】---- 公众号支付相关推荐
- 微信开发------------公众号支付统一下单整个流程
申请微信支付在这里:https://blog.csdn.net/dmw412724/article/details/82735906 微信支付后台搭建(阅读本文的基础):https://blog.cs ...
- php 公众号内h5支付宝支付宝支付宝支付宝支付,微信浏览器中支付宝wap支付和微信JSAPI公众号支付...
手机浏览器只有支付宝wap支付,微信浏览器中出现支付宝wap支付和微信JSAPI公众号支付,其中支付宝wap在线支付没有在新窗口打开(兼容大部分手机),Thinkphp3.2公众号支付 下载资源 下载 ...
- 微信jsapi支付获取code_微信JSAPI公众号支付在部分机型上出现appid参数错误的解决办法 - YangJunwei...
都说微信支付的坑比较多,老杨感觉还行,就是开始周期和调试过程比较费时费力-_-! 今儿在调试一个基于微信JSAPI公众号的支付项目时发现,部分机型(比如iphone7/vivo-x6d)中微信支付获取 ...
- 微信跨公众号支付(appid 与 openid 不匹配)-koa
实现不同主体公众号的跨公众号支付其实原理很简单,就下面几点: 1.用一个公众号的appid来进行收款 2.这个公众号的appid需要和商户号绑定 3.用这个appid来获取用户的openid 进过上面 ...
- 公众号php支付接口开发,公众号支付接口的开发
这次给大家带来公众号支付接口的开发,公众号支付接口开发的注意事项有哪些,下面就是实战案例,一起来看一下. 公众号支付就是在微信里面的H5页面唤起微信支付,不用扫码即可付款的功能.做这个功能首先要明确的 ...
- php微信统一公众号支付接口,微信公众号支付怎么实现统一下单接口
微信公众号支付怎么实现统一下单接口 发布时间:2021-03-12 09:44:45 来源:亿速云 阅读:89 作者:小新 这篇文章将为大家详细讲解有关微信公众号支付怎么实现统一下单接口,小编觉得挺实 ...
- 微信跨公众号支付(appid 与 openid 不匹配)
实现不同主体公众号的跨公众号支付其实原理很简单,就下面几点: 1.用一个公众号的appid来进行收款 2.这个公众号的appid需要和商户号绑定 3.用这个appid来获取用户的openid 进过上面 ...
- 微信开发公众号页面配置JS-SDK调用微信接口
一:前期准备, 1,首先你需要一台服务器,:阿里云或者腾讯云啥的,最便宜的可以选择类似阿里云这种轻量级服务器,和域名配置好A记录. 2,需要在服务器打架node环境:可以找客服帮忙基本配置环境搭好. ...
- 微信开发 - 公众号授权登录方案(含跳转关注公众号)
公众号授权登录代码(含跳转关注公众号) 注意: 跳转微信公众号页: https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MzU5 ...
- 公众号php视频教程,微信开发公众号平台视频教程
微信公众平台,给个人.企业和组织提供业务服务与用户管理能力的全新服务平台.微信公众平台主要有实时交流.消息发送和素材管理.用户可以对公众账户的粉丝分组管理.实时交流,同时也可以使用高级功能-编辑模式和 ...
最新文章
- 用双注意力模块来做语义分割
- 全栈工程师的学习笔记与工作记录
- 云在物联网中的惊人优势 | 技术头条
- 如何应对互联网界的奇葩面试题!
- oracle创建视图类型为日期,oracle创建视图包含clob字段,报错:数据类型不一致:应为-,但却获得CLOB...
- 大数据产业链结构_【数据结构 | 大整型】
- Unity3D基础33:物理射线
- 无盘工作站与VMware View虚拟桌面对比
- 结构体、文件操作、指针
- jupyter notebook 修改主题、字体、字号等
- 点滴记录笔记_持续更新
- 海外文摘杂志海外文摘杂志社海外文摘编辑部2022年第4期目录
- 迪杰斯算法c语言,欧博体育APP-欧博体育APP
- 既然android service是运行在主线程中的,那service还有什么用?
- 神经网络常用的训练方式,神经网络训练过程详解
- 学习笔记——Jupyter notebook快速入门教程
- 神经网络学习笔记(五) 径向基函数神经网络
- chart.js报错“Canvas is already in use. Chart ...must be destroyed before the canvas can be reused ”
- 三阶矩阵的lu分解详细步骤_计算方法(三)矩阵分解2-LU分解
- 一次当晋级评委的经历
热门文章
- 软工尾声-提问回顾与个人总结
- 从一路赞美到嘘声不断 90后创业热潮已宣告死亡
- Centos 安装 KVM虚拟化工具 超云服务器 VMware
- 20条技巧,让Chrome超越Firefox
- 一、SpringCloud五大神兽之Eureka(eurekaServer集群)
- mysql里all什么意思_mysql中all的用法是什么
- 【转载】【整理】《三五个人十来条枪 如何走出软件作坊成为开发正规军》合集 [更新至41]...
- 决策树和提升树的区别_决策树提升技术比较
- 大数据的Hadoop架构有哪些优势?
- 微信小程序实战教程-闫涛-专题视频课程