本篇主要記錄微信支付中公衆號及H5支付全過程。javascript
公衆號或者服務號(並開通微信支付功能)、商戶平臺中開通JSAPI支付、H5支付。php
公衆號或者服務號中 -------開發-------開發者工具---------web開發者工具-------綁定爲開發者html
公衆號或者服務號中 -------公衆號設置--------功能設置 :填寫業務域名、JS安全域名、網頁受權域名 示例:pay.one.com前端
商戶平臺中--------產品中心-------開發配置------JSAPI支付受權目錄填寫:http://pay.one.com/ http://pay.one.com/WeChatPay/PubPay/-----H5支付填寫:pay.one.comjava
若對配置還有疑問,可參考官方文檔:jquery
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842git
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6github
本Demo是基於Payment 的SDK開發。具體詳情可參考:https://github.com/Essensoft/Paymentweb
首先 使用Nuget安裝payment:api
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?appid=@ViewBaig.AppId&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> /// 用戶惟一標識。請注意,在未關注公衆號時,用戶訪問公衆號的網頁,也會產生一個用戶和公衆號惟一的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支付是指再除開微信瀏覽器之外的移動端瀏覽器上進行微信回覆操做。
和上面步驟大致一致,有幾個地方須要注意
1:客戶端IP問題:H5支付的時候,微信支付系統會根據客戶端調起的當前Ip 做爲支付Ip,若發現 發起支付請求時,ip有問題,則會支付失敗,或者提示系統繁忙。這裏貼一下我獲取IP的代碼:
Utils.GetUserIp(_accessor.HttpContext);//頁面上調用 /// <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:若調起微信支付成功後,默認回調到支付首頁,若須要設置回調頁面,則能夠再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:支付結果通知:
注意:
一、一樣的通知可能會屢次發送給商戶系統。商戶系統必須可以正確處理重複的通知。
二、後臺通知交互時,若是微信收到商戶的應答不符合規範或超時,微信會斷定本次通知失敗,從新發送通知,直到成功爲止(在通知一直不成功的狀況下,微信總共會發起10次通知,通知頻率爲15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 總計 24h4m),但微信不保證通知最終必定能成功。
三、在訂單狀態不明或者沒有收到微信支付結果通知的狀況下,建議商戶主動調用微信支付【查詢訂單API】確認訂單狀態。
特別提醒:
一、商戶系統對於支付結果通知的內容必定要作簽名驗證,並校驗返回的訂單金額是否與商戶側的訂單金額一致,防止數據泄漏致使出現「假通知」,形成資金損失。
二、當收到通知進行處理時,首先檢查對應業務數據的狀態,判斷該通知是否已經處理過,若是沒有處理過再進行處理,若是處理過直接返回結果成功。在對業務數據進行狀態檢查和處理以前,要採用數據鎖進行併發控制,以免函數重入形成的數據混亂。
最後能夠測試下H5支付,查看返回的Log:
本文只是拋磚引玉,更多具體支付場景和代碼,還需各位看官結合本身項目量身定作。
更多示例Demo可入羣獲取。