前言
準備篇
公眾號或者服務號(并開通微信支付功能)、商戶平臺中開通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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
//微信支付請求客戶端(用于處理請求與響應) 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
頁面:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
@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/[email protected]&redirect_uri=' + encodeURIComponent(local)+'&response_type=code&scope=snsapi_base&state=a#wechat_redirect'; }); }); </ script > |
此時:PayBack Action如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
[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是網頁授權 返回的實體:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
/// 獲取網頁授權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 ; } } |
最后 貼一下支付成功后的回調函數:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
[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有問題,則會支付失敗,或者提示系統繁忙。這里貼一下我獲取IP的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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中拼接:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
/// <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、后臺通知交互時,如果微信收到商戶的應答不符合規范或超時,微信會判定本次通知失敗,重新發送通知,直到成功為止(在通知一直不成功的情況下,微信總共會發起10次通知,通知頻率為15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 總計 24h4m),但微信不保證通知最終一定能成功。
3、在訂單狀態不明或者沒有收到微信支付結果通知的情況下,建議商戶主動調用微信支付【 查詢訂單API 】確認訂單狀態。
特別提醒:
1、商戶系統對于支付結果通知的內容一定要做 簽名驗證,并校驗返回的訂單金額是否與商戶側的訂單金額一致 ,防止數據泄漏導致出現“假通知”,造成資金損失。
2、當收到通知進行處理時,首先檢查對應業務數據的狀態,判斷該通知是否已經處理過,如果沒有處理過再進行處理,如果處理過直接返回結果成功。在對業務數據進行狀態檢查和處理之前,要采用數據鎖進行并發控制,以避免函數重入造成的數據混亂。
最后可以測試下H5支付,查看返回的Log:
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:http://www.cnblogs.com/zhangxiaoyong/p/10588241.html