.NET Core之微信支付之公眾號、H5支付詳解
前言
本篇主要記錄微信支付中公眾號及H5支付全過程。
準(zhǔn)備篇
公眾號或者服務(wù)號(并開通微信支付功能)、商戶平臺中開通JSAPI支付、H5支付。
配置篇
公眾號或者服務(wù)號中 -------開發(fā)-------開發(fā)者工具---------web開發(fā)者工具-------綁定為開發(fā)者
公眾號或者服務(wù)號中 -------公眾號設(shè)置--------功能設(shè)置 :填寫業(yè)務(wù)域名、JS安全域名、網(wǎng)頁授權(quán)域名 示例:pay.one.com
商戶平臺中--------產(chǎn)品中心-------開發(fā)配置------JSAPI支付授權(quán)目錄填寫: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
開發(fā)篇
JSAPI支付
本Demo是基于Payment 的SDK開發(fā)。具體詳情可參考: 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:
//微信支付請求客戶端(用于處理請求與響應(yīng))
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 = "" //此處需進(jìn)行授權(quán) 獲取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"] = "請返回上級重新進(jìn)入此頁面以獲取最新數(shù)據(jù)";
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 //此處需進(jìn)行授權(quán) 獲取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);
// 將參數(shù)(parameter)給 公眾號前端 讓他在微信內(nèi)H5調(diào)起支付(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();
}
注意:公眾號或者微信內(nèi)支付,需要授權(quán)獲取到用戶的OpenId。所以,此處我們還需要進(jìn)行微信授權(quán),而授權(quán)方式有兩種,一種是靜默授權(quán)、一種是需要用戶同意,區(qū)別是 靜默授權(quán)只能拿到Openid,而經(jīng)用戶同意后可拿到 微信頭像、昵稱、性別等其他信息。
具體可參閱文檔: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
頁面:
@using Newtonsoft.Json
@model WeChatPayPubPayViewModel
@{
ViewData["Title"] = "公眾號支付-統(tǒng)一下單";
}
<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. + 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("授權(quán)成功");
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 //此處需進(jìn)行授權(quán) 獲取OpenId
};
var response = await _client.ExecuteAsync(request);
_logger.LogWarning($"統(tǒng)一下單接口返回:{response.ReturnCode}");
if (response.ReturnCode == "SUCCESS" && response.ResultCode == "SUCCESS")
{
var req = new WeChatPayH5CallPaymentRequest
{
Package = "prepay_id=" + response.PrepayId
};
var parameter = await _client.ExecuteAsync(req);
// 將參數(shù)(parameter)給 公眾號前端 讓他在微信內(nèi)H5調(diào)起支付(https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6)
ViewData["parameter"] = JsonConvert.SerializeObject(parameter);
_logger.LogWarning($"統(tǒng)一下單成功,即將調(diào)起微信支付:{ViewData["parameter"].ToString()}");
ViewData["response"] = response.Body;
return View();
}
ViewData["response"] = response.Body;
return View();
}
其中:OAuthToken是網(wǎng)頁授權(quán) 返回的實(shí)體:
/// 獲取網(wǎng)頁授權(quán)token時,返回的實(shí)體
/// </summary>
public class OAuthToken : BaseRes
{
/// <summary>
/// 網(wǎng)頁授權(quán)接口調(diào)用憑證。注意:此access_token與基礎(chǔ)支持的access_token不同
/// </summary>
[JsonProperty("access_token")]
public string AccessToken { get; set; }
private int _expiresIn;
/// <summary>
/// access_token接口調(diào)用憑證超時時間,單位(秒)
/// </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>
/// 用戶唯一標(biāo)識。請注意,在未關(guān)注公眾號時,用戶訪問公眾號的網(wǎng)頁,也會產(chǎn)生一個用戶和公眾號唯一的openid
/// </summary>
[JsonProperty("openid")]
public string Openid { get; set; }
/// <summary>
/// 用戶授權(quán)的作用域,使用逗號(,)分隔
/// </summary>
[JsonProperty("scope")]
public string Scope { get; set; }
[JsonProperty("expires_time")]
public DateTime ExpiresTime { get; set; }
/// <summary>
/// 只有在用戶將公眾號綁定到微信開放平臺賬號后,才會出現(xiàn)該字段
/// </summary>
[JsonProperty("unionid")]
public string Unionid { get; set; }
}
最后 貼一下支付成功后的回調(diào)函數(shù):
[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>
/// 統(tǒng)一下單支付結(jié)果通知
/// </summary>
/// <returns></returns>
[Route("unifiedorder")]
[HttpPost]
public async Task<IActionResult> Unifiedorder()
{
try
{
_logger.LogWarning($"進(jìn)入回調(diào)");
var payconfig = OpenApi.GetPayConfig();
var notify = await _client.ExecuteAsync<WeChatPayUnifiedOrderNotify>(Request);
_logger.LogWarning($"返回狀態(tài)碼:{notify.ReturnCode}");
if (notify.ReturnCode == "SUCCESS")
{
_logger.LogWarning($"業(yè)務(wù)結(jié)果碼:{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($"回調(diào)失敗:{ex.Message}");
return NoContent();
}
}
}
然后測試一下支付,查看服務(wù)器Log如下:

H5支付
H5支付是指再除開微信瀏覽器以外的移動端瀏覽器上進(jìn)行微信回復(fù)操作。
和上面步驟大體一致,有幾個地方需要注意
1:客戶端IP問題:H5支付的時候,微信支付系統(tǒng)會根據(jù)客戶端調(diào)起的當(dāng)前Ip 作為支付Ip,若發(fā)現(xiàn) 發(fā)起支付請求時,ip有問題,則會支付失敗,或者提示系統(tǒng)繁忙。這里貼一下我獲取IP的代碼:
Utils.GetUserIp(_accessor.HttpContext);//頁面上調(diào)用
/// <summary>
/// 穿過代理服務(wù)器獲取真實(shí)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類型應(yīng)該是:MWEB
3:若調(diào)起微信支付成功后,默認(rèn)回調(diào)到支付首頁,若需要設(shè)置回調(diào)頁面,則可以再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);
}

更多詳細(xì)可參考文檔: https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_4
4:支付結(jié)果通知:
注意:
1、同樣的通知可能會多次發(fā)送給商戶系統(tǒng)。商戶系統(tǒng)必須能夠正確處理重復(fù)的通知。
2、后臺通知交互時,如果微信收到商戶的應(yīng)答不符合規(guī)范或超時,微信會判定本次通知失敗,重新發(fā)送通知,直到成功為止(在通知一直不成功的情況下,微信總共會發(fā)起10次通知,通知頻率為15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 總計(jì) 24h4m),但微信不保證通知最終一定能成功。
3、在訂單狀態(tài)不明或者沒有收到微信支付結(jié)果通知的情況下,建議商戶主動調(diào)用微信支付【 查詢訂單API 】確認(rèn)訂單狀態(tài)。
特別提醒:
1、商戶系統(tǒng)對于支付結(jié)果通知的內(nèi)容一定要做 簽名驗(yàn)證,并校驗(yàn)返回的訂單金額是否與商戶側(cè)的訂單金額一致 ,防止數(shù)據(jù)泄漏導(dǎo)致出現(xiàn)“假通知”,造成資金損失。
2、當(dāng)收到通知進(jìn)行處理時,首先檢查對應(yīng)業(yè)務(wù)數(shù)據(jù)的狀態(tài),判斷該通知是否已經(jīng)處理過,如果沒有處理過再進(jìn)行處理,如果處理過直接返回結(jié)果成功。在對業(yè)務(wù)數(shù)據(jù)進(jìn)行狀態(tài)檢查和處理之前,要采用數(shù)據(jù)鎖進(jìn)行并發(fā)控制,以避免函數(shù)重入造成的數(shù)據(jù)混亂。
最后可以測試下H5支付,查看返回的Log:

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
ASP.NET向Javascript傳遞變量兩種實(shí)現(xiàn)方法
ASP.NET向Javascript傳遞變量兩種實(shí)現(xiàn)方法,需要的朋友可以參考下2012-12-12
asp.net顯示相同數(shù)字相乘的結(jié)果,直到數(shù)值大于150為止
老師布置Insus.NET做的第二道題,題目如標(biāo)題。感興趣的網(wǎng)友也可以練習(xí)練習(xí)?,F(xiàn)在Insus.NET的作答如下,但老師還沒有看,因此答案是否正確或是最好的,還不能確定,只是供參考2012-05-05
asp.net mvc CodeFirst模式數(shù)據(jù)庫遷移步驟詳解
這篇文章主要為大家詳細(xì)介紹了asp.net mvc CodeFirst模式數(shù)據(jù)庫遷移步驟,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10
利用VS2019創(chuàng)建Web項(xiàng)目并發(fā)送到IIS及IIS與ASP.NET配置教程
這篇文章主要介紹了利用VS2019創(chuàng)建Web項(xiàng)目,并發(fā)送到IIS,以及IIS與ASP.NET配置,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03
asp.net viewstate 回發(fā)機(jī)制
ASP.NET中,為了模擬Winform中的事件響應(yīng)機(jī)制,微軟的工程師真是煞費(fèi)苦心,發(fā)明了“回發(fā)”機(jī)制,使得編寫WEB頁面變得和Winform一樣簡單。2010-03-03

