1. .NET Core之微信支付之公众号、H5支付详解

     更新时间:2019年03月25日 09:38:06   作者:潇十一郎   我要评论

    这篇文章主要介绍了.NET Core之微信支付之公众号、H5支付篇,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

    前言

    本篇主要记录微信支付中公众号及H5支付全过程。

    准备篇

    公众号或者服务号(并开通微信支付功能)、商户平台中开通JSAPI支付、H5支付。

    配置篇

    公众号或者服务号中 -------开发-------开发者工具---------web开发者工具-------绑定为开发者

    公众号或者服务号中 -------公众号设置--------功能设置   :填写业务域名、JS安全域名、网页授权域名 示例:pay.one.com

    商户平台中--------产品中心-------开发配置------JSAPI支付授权目录填写:http://pay.one.com/    http://pay.one.com/WeChatPay/PubPay/-----H5支付填写:pay.one.com

    若对配置还有疑问,可参考官方文档:

    https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842

    https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6

    开发篇

    JSAPI支付

    本Demo是基于Payment 的SDK开发。具体详情可参考: https://github.com/Essensoft/Payment

    首先 使用Nuget安装payment:

    Install-Package  :Essensoft.AspNetCore.Payment.WeChatPay -Version 2.3.2

    建一个Model: WeChatPayPubPayViewModel

    public class WeChatPayPubPayViewModel
      {
        [Required]
        [Display(Name = "out_trade_no")]
        public string OutTradeNo { get; set; }
    
        [Required]
        [Display(Name = "body")]
        public string Body { get; set; }
    
        [Required]
        [Display(Name = "total_fee")]
        public int TotalFee { get; set; }
    
        [Required]
        [Display(Name = "spbill_create_ip")]
        public string SpbillCreateIp { get; set; }
    
        [Required]
        [Display(Name = "notify_url")]
        public string NotifyUrl { get; set; }
    
        [Required]
        [Display(Name = "trade_type")]
        public string TradeType { get; set; }
    
        [Required]
        [Display(Name = "openid")]
        public string OpenId { get; set; }
      }

    WeChatPayController:

    //微信支付请求客户端(用于处理请求与响应)
    private readonly IWeChatPayClient _client;
    private readonly ILogger<WeChatPayController> _logger;
    
     private IHttpContextAccessor _accessor;
    
    public WeChatPayController(IWeChatPayClient client, IHttpContextAccessor accessor, ILogger<WeChatPayController> logger)
        {
          _client = client;
          _accessor = accessor;
          _logger = logger;
        }
        /// <summary>
        /// 公众号支付
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public IActionResult PubPay()
        {
          WeChatPayPubPayViewModel payModel=new WeChatPayPubPayViewModel()
          {
            Body = "微信公众号支付测试",
            OutTradeNo = DateTime.Now.ToString("yyyyMMddHHmmssfff"),
            TotalFee = 1,//分 单位
            SpbillCreateIp = "127.0.0.1",
            NotifyUrl = "http://pay.one.com/notify/wechatpay/unifiedorder",
            TradeType = "JSAPI",
            OpenId = "" //此处需进行授权 获取OpenId
          };
          return View(payModel);
        }
    
        /// <summary>
        /// 公众号支付
        /// </summary>
        /// <param name="viewModel"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<IActionResult> PubPay(WeChatPayPubPayViewModel viewModel)
        {
          if(string.IsNullOrEmpty(viewModel.OpenId))
          {
            ViewData["response"] = "请返回上级重新进入此页面以获取最新数据";
            return View();
          }
    
          var request = new WeChatPayUnifiedOrderRequest
          {
            Body = viewModel.Body,
            OutTradeNo = viewModel.OutTradeNo,
            TotalFee = viewModel.TotalFee,
            SpbillCreateIp = viewModel.SpbillCreateIp,
            NotifyUrl = viewModel.NotifyUrl,
            TradeType = viewModel.TradeType,
            OpenId = viewModel.OpenId //此处需进行授权 获取OpenId
          };
          var response = await _client.ExecuteAsync(request);if (response.ReturnCode == "SUCCESS" && response.ResultCode == "SUCCESS")
          {
            var req = new WeChatPayH5CallPaymentRequest
            {
              Package = "prepay_id=" + response.PrepayId
            };
            var parameter = await _client.ExecuteAsync(req);
            // 将参数(parameter)给 公众号前端 让他在微信内H5调起支付(https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6)
            ViewData["parameter"] = JsonConvert.SerializeObject(parameter);
            ViewData["response"] = response.Body;
            return View();
          }
          ViewData["response"] = response.Body;
          return View();
        }
    
    

    注意:公众号或者微信内支付,需要授权获取到用户的OpenId。所以,此处我们还需要进行微信授权,而授权方式有两种,一种是静默授权、一种是需要用户同意,区别是 静默授权只能拿到Openid,而经用户同意后可拿到 微信头像、昵称、性别等其他信息。

    具体可参阅文档: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842

    页面:

    @using Newtonsoft.Json
    @model WeChatPayPubPayViewModel
    @{
      ViewData["Title"] = "公众号支付-统一下单";
    }
    <nav aria-label="breadcrumb">
      <ol class="breadcrumb">
        <li class="breadcrumb-item"><a asp-controller="WeChatPay" asp-action="Index">微信支付</a></li>
        <li class="breadcrumb-item active" aria-current="page">@ViewData["Title"]</li>
      </ol>
    </nav>
    <br />
    <div class="card">
      <div class="card-body">
        <form asp-controller="WeChatPay" asp-action="PubPay">
          <div asp-validation-summary="All" class="text-danger"></div>
          <div class="form-group">
            <label asp-for="OutTradeNo"></label>
            <input type="text" class="form-control" asp-for="OutTradeNo" value="@Model?.OutTradeNo" />
          </div>
          <div class="form-group">
            <label asp-for="Body"></label>
            <input type="text" class="form-control" asp-for="Body" value="@Model?.Body" />
          </div>
          <div class="form-group">
            <label asp-for="TotalFee"></label>
            <input type="text" class="form-control" asp-for="TotalFee" value="@Model?.TotalFee" />
          </div>
          <div class="form-group">
            <label asp-for="SpbillCreateIp"></label>
            <input type="text" class="form-control" asp-for="SpbillCreateIp" value="@Model?.SpbillCreateIp" />
          </div>
          <div class="form-group">
            <label asp-for="NotifyUrl"></label>
            <input type="text" class="form-control" asp-for="NotifyUrl" value="@Model?.NotifyUrl" />
          </div>
          <div class="form-group">
            <label asp-for="TradeType"></label>
            <input type="text" class="form-control" asp-for="TradeType" value="@Model?.TradeType" />
          </div>
          <div class="form-group">
            <label asp-for="OpenId"></label>
            <input type="text" class="form-control" asp-for="OpenId" value="@Model?.OpenId" />
          </div>
          <button type="submit" class="btn btn-primary">提交请求</button>
          <button type="button" class="btn btn-success" id="PayNow">立即支付</button>
        </form>
        <hr />
        <form class="form-horizontal">
          <div class="form-group">
            <label>Response:</label>
            <textarea class="form-control" rows="10">@ViewData["response"]</textarea>
          </div>
          <div class="form-group">
            <label>Parameter:</label>
            <textarea class="form-control" rows="3">@ViewData["parameter"]</textarea>
          </div>
        </form>
      </div>
    </div>
    @section Scripts {
      @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
    }
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script type="text/javascript">
      $(function () {
        $("#PayNow").on('click', function () {
          const local = "http://pay.one.com/WeChatPay/PayBack/"; 
           window.location.href ='https://open.weixin.qq.com/connect/oauth2/authorize?[email protected]&redirect_uri=' + encodeURIComponent(local)+'&response_type=code&scope=snsapi_base&state=a#wechat_redirect';
        });
      
      });
    
    </script>

    此时:PayBack Action如下:

     [HttpGet]
        public async Task<IActionResult> PayBack()
        {
          var code = Request.Query["code"];
          var state = Request.Query["state"];
          OAuthToken tokenModel = new OAuthToken();
          //通过code换取token
          if (!string.IsNullOrEmpty(code))
          {
            _logger.LogWarning("授权成功");
            ViewBag.Code = code;
            tokenModel = OauthApi.GetAuthToken(code, wechatAppId);
          }
    
          var request = new WeChatPayUnifiedOrderRequest
          {
            Body = "微信公众号支付测试",
            OutTradeNo = DateTime.Now.ToString("yyyyMMddHHmmssfff"),
            TotalFee = 1,//分 单位
            SpbillCreateIp = "127.0.0.1",
            NotifyUrl = "http://pay.one.com/notify/wechatpay/unifiedorder",
            TradeType = "JSAPI",
            OpenId = tokenModel.Openid //此处需进行授权 获取OpenId
          };
          var response = await _client.ExecuteAsync(request);
          _logger.LogWarning($"统一下单接口返回:{response.ReturnCode}");
    
          if (response.ReturnCode == "SUCCESS" && response.ResultCode == "SUCCESS")
          {
            var req = new WeChatPayH5CallPaymentRequest
            {
              Package = "prepay_id=" + response.PrepayId
            };
            var parameter = await _client.ExecuteAsync(req);
            // 将参数(parameter)给 公众号前端 让他在微信内H5调起支付(https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6)
            ViewData["parameter"] = JsonConvert.SerializeObject(parameter);
            _logger.LogWarning($"统一下单成功,即将调起微信支付:{ViewData["parameter"].ToString()}");
            ViewData["response"] = response.Body;
            return View();
          }
          ViewData["response"] = response.Body;
    
          
          return View();
        }
    
    

    其中:OAuthToken是网页授权 返回的实体:

    /// 获取网页授权token时,返回的实体
      /// </summary>
      public class OAuthToken : BaseRes
      {
        /// <summary>
        /// 网页授权接口调用凭证。注意:此access_token与基础支持的access_token不同
        /// </summary>
        [JsonProperty("access_token")]
        public string AccessToken { get; set; }
        private int _expiresIn;
        /// <summary>
        /// access_token接口调用凭证超时时间,单位(秒)
        /// </summary>
        [JsonProperty("expires_in")]
        public int ExpiresIn
        {
          get { return _expiresIn; }
          set
          {
            ExpiresTime = DateTime.Now.AddSeconds(value);
            _expiresIn = value;
          }
        }
        /// <summary>
        /// 用于刷新access_token
        /// </summary>
        [JsonProperty("refresh_token")]
        public string RefreshToken { get; set; }
        /// <summary>
        /// 用户唯一标识。请注意,在未关注公众号时,用户访问公众号的网?#24120;不?#20135;生一个用户和公众号唯一的openid
        /// </summary>
        [JsonProperty("openid")]
        public string Openid { get; set; }
        /// <summary>
        /// 用户授权的作用域,使用逗号(,)分隔
        /// </summary>
        [JsonProperty("scope")]
        public string Scope { get; set; }
        [JsonProperty("expires_time")]
        public DateTime ExpiresTime { get; set; }
        /// <summary>
        /// 只有在用户将公众号绑定到微信开放平台账号后,才会出现该字段
        /// </summary>
        [JsonProperty("unionid")]
        public string Unionid { get; set; }
      }
    
    

    最后 贴一下支付成功后的回调函数:

    [Route("notify/wechatpay")]
      public class WeChatPayNotifyController : Controller
      {
        private readonly IWeChatPayNotifyClient _client;
        private readonly ILogger<WeChatPayNotifyController> _logger;
        public WeChatPayNotifyController(IWeChatPayNotifyClient client,ILogger<WeChatPayNotifyController> logger)
        {
          _client = client;
          _logger = logger;
        }
    
        /// <summary>
        /// 统一下单支付结果通知
        /// </summary>
        /// <returns></returns>
        [Route("unifiedorder")]
        [HttpPost]
        public async Task<IActionResult> Unifiedorder()
        {
          try
          {
            _logger.LogWarning($"进入回调");
            var payconfig = OpenApi.GetPayConfig();
            var notify = await _client.ExecuteAsync<WeChatPayUnifiedOrderNotify>(Request);
            _logger.LogWarning($"返回状态码:{notify.ReturnCode}");
    
            if (notify.ReturnCode == "SUCCESS")
            {
              _logger.LogWarning($"业务结果码:{notify.ResultCode}");
    
              if (notify.ResultCode == "SUCCESS")
              {
                _logger.LogWarning($"支付方式:{notify.TradeType}");
                _logger.LogWarning($"商户订单号:{notify.OutTradeNo}");
                _logger.LogWarning($"微信支付订单号:{notify.TransactionId}");
                _logger.LogWarning($"支付金额:{notify.TotalFee}");
                return WeChatPayNotifyResult.Success;
              }
            }
            return NoContent();
          }
          catch(Exception ex)
          {
            _logger.LogWarning($"回调失败:{ex.Message}");
            return NoContent();
          }
        }
    }

    然后测试一下支付,查看服务器Log如下:

    H5支付

    H5支付是指再除开微信浏览器以外的移动端浏览器上进行微信回复操作。

    和上面步骤大体一致,有几个地方需要注意

    1:客户端IP问题:H5支付的时候,微信支付系统会根据客户端调起的当前Ip 作为支付Ip,若发现 发起支付请求时,ip有问题,则会支付失败,或者提示系统繁忙。这里贴一下?#19968;?#21462;IP的代码:

    Utils.GetUserIp(_accessor.HttpContext);//页面?#31995;?#29992;
    
    
        /// <summary>
        /// 穿过代理服务器获取真实IP
        /// </summary>
        /// <returns></returns>
        public static string GetUserIp(this HttpContext context)
        {
          var ip = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
          if (string.IsNullOrEmpty(ip))
          {
            ip = context.Connection.RemoteIpAddress.ToString();
          }
          return ip;
          
        }
    
    

    2:TradeType类型应该是:MWEB

    3:若调起微信支付成功后,默认回调到支付首?#24120;?#33509;需要设置回调页面,则可以再URl中拼接:

      /// <summary>
        /// H5支付
        /// </summary>
        /// <param name="viewModel"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<IActionResult> H5Pay(WeChatPayH5PayViewModel viewModel)
        {
          var request = new WeChatPayUnifiedOrderRequest
          {
            Body = viewModel.Body,
            OutTradeNo = viewModel.OutTradeNo,
            TotalFee = viewModel.TotalFee,
            SpbillCreateIp = viewModel.SpbillCreateIp,
            NotifyUrl = viewModel.NotifyUrl,
            TradeType = viewModel.TradeType
          };
          var response = await _client.ExecuteAsync(request);
    
          // mweb_url为拉起微信支付收银台的中间页面,可通过访问该url来拉起微信客户端,完成支付,mweb_url的有效期为5分钟。
          if (response.MwebUrl == null)
          {
            ViewData["response"] = response.ReturnMsg;
            return View();
          }
          return Redirect(response.MwebUrl);
        }

    更多详细可参考文档: https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_4

    4:支付结果通知:

    注意:

    1、同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。

    2、后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信会判定本次通知失败,重新发送通知,直到成功为止(在通知一直不成功的情况下,微信总?#19981;?#21457;起10次通知,通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m),但微信不保证通知最终一定能成功。

    3、在订单状态不明或者没有收到微信支付结果通知的情况下,建议商户主动调用微信支付【 查询订单API 】确认订单状态。

    特别提醒:

    1、商户系统对于支付结果通知的内容一定要做 签名验证,并校验返回的订单金额是否与商户侧的订单金额一致 ,防止数据泄漏导致出现“假通知?#20445;?#36896;成?#24335;?#25439;失。

    2、当收到通知进?#20889;?#29702;时,首先检查对应业务数据的状态,判?#32454;?#36890;知是否已经处理过,如果没有处理过再进?#20889;?#29702;,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。

    最后可以测试下H5支付,查看返回的Log:

    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

    相关文章

    • .NET实现WebSocket服务端?#35789;?#36890;信实例

      .NET实现WebSocket服务端?#35789;?#36890;信实例

      本篇文章主要介绍了.NET实现?#35789;?#36890;信,WebSocket服务端实例 ,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
      2017-02-02
    • .net基础收集汇总

      .net基础收集汇总

      最近的面试让我知道基础知识的重要性,而我也每天都在网上找一些基础题来?#30784;?#20854;实面试无非都是一些理论基础,只有基础过关了,才会被?#23454;?#25216;术性的问题,所以第一关一定要打好
      2013-07-07
    • asp.net操作过程中常见错误的解决方法

      asp.net操作过程中常见错误的解决方法

      这篇文章主要介绍了asp.net操作过程中常见错误的解决方法,主要有IIS无法识别ASP.NET、 SQL Server不允许进行远程连接可能会导致此失败等问题,?#34892;?#36259;的小伙伴们可以参?#23478;?#19979;
      2015-10-10
    • asp.net替换和?#25351;磆tml特殊字符

      asp.net替换和?#25351;磆tml特殊字符

      替换html中的特殊字符需要进行替换的文本。替换完的文本。
      2008-05-05
    • asp.net MaxLengthValidator 最大长度验证控件代码

      asp.net MaxLengthValidator 最大长度验证控件代码

      如果数据库字段为varchar或char类型,ASP.NET控件在可输入汉字的情况下,MaxLength属性不能保证在保存到数据库时不发生截断错误,因此写了一个最大长度验证控件,还可用于多行文本框。
      2009-12-12
    • ASP.NET实现个人信息注册页面并跳转显示

      ASP.NET实现个人信息注册页面并跳转显示

      这篇文章主要介绍了ASP.NET实现个人信息注册页面并跳转显示的相关资料,本文图文并茂给大家介绍的非常详细,需要的朋友可以参考下
      2016-11-11
    • ASP.NET常用小技巧

      ASP.NET常用小技巧

      这篇文章主要介绍了ASP.NET常用小技巧,非常实用,需要的朋友可以参考下
      2015-09-09
    • URL中去除?#20184;?#21442;数实现C#代码

      URL中去除?#20184;?#21442;数实现C#代码

      URL中去除?#20184;?#21442;数在项目开发中还是很常见的,本文将介绍下它在c#代码中的实现,?#34892;?#36259;的朋友可以参考下哈
      2013-04-04
    • 使用ASP.NET模板生成HTML静态页面的五种方案

      使用ASP.NET模板生成HTML静态页面的五种方案

      使用ASP.NET模版生成HTML静态页面并不是难事,主要是使各个静态页面间的关联和链接如何保持完整。本文介绍了使用ASP.NET模版生成HTML静态页面的五种方案
      2011-11-11
    • Asp.Net 程序错误Runtime Error原因与解决

      Asp.Net 程序错误Runtime Error原因与解决

      提示这个,?#36824;?#24590;么改配置文件的设置都不行,下面是修正方法,大家可以试试。
      2010-03-03

    最新评论

    山东群英会开奖查询