支付這一塊也算是項目開發常遇到的功能,尤其是電商這塊,支付必不可少,而現在常用的兩大支付:1,微信支付 2 ,支付寶支付。但是我這里我要說的就是這個坑爹的微信支付,因為整體來說他沒有支付寶支付顯得友好,為什么呢?因為他沒有測試環境,如果要開發這塊必須要在實際環境中進行,真他媽操蛋!而這一塊支付寶就比較友好了,他給開發者提供了沙箱測試環境,是真的不錯,至於怎末操作,這里就不多說了,后續會詳細介紹支付寶的沙箱支付。
按理說在實際環境下測試開發也沒啥問題,但是對於好多想學微信支付的同僚們來說就顯得不友好了,因為不是所有的軟件都會包含支付功能這一塊的,有的可能做了幾年軟件開發都沒有接觸到支付這一塊,別不相信,這種情況普遍存在。所以呀,微信支付這一塊官網還是得上點心,搞個測試環境,照顧照顧開發者。哈哈哈哈
微信支付主要是商家申請賬號的,要到微信商戶平台上去注冊賬號,還有申請一個微信公眾號平台賬號。至於這些怎末操作,這里就不多說了,自己看官網,講的還是比較細致的。看一下就了解了。
微信支付有很多中類型,這里就說native支付的方式:
我們看一下Native支付,官方給的接口:
本次就先看一下Native下單和支付成功之后的回調。
下面我們就根據官方的文檔一步一步操作:
首先看接口:
請求示例:
返回參數:
從上面由請求接口,請求參數,返回參數,我們可以根據官方要求去做:
微信支付需要的基本配置信息:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; namespace WeChat.Api.Common {
注意:appi appSecret是申請微信公眾號平台獲取的 后面的幾個是微信商戶平台獲取的。 public class WeChatConfig { /// <summary> /// 直連商戶申請的公眾號或移動應用appid。 /// </summary> public static string appid => ""; /// <summary> /// AppSecret,app端加密解密使用 /// </summary> public static string AppSecret => ""; /// <summary> /// 密鑰,用商戶平台上設置的APIv3密鑰【微信商戶平台—>賬戶設置—>API安全—>設置APIv3密鑰】,記為key; /// </summary> public static string APIV3Key => ""; /// <summary> /// 直連商戶的商戶號,由微信支付生成並下發。 /// </summary> public static string mchid => ""; /// <summary> /// 證書序列號 /// 查看證書序列號:https://wechatpay-api.gitbook.io/wechatpay-api-v3/chang-jian-wen-ti/zheng-shu-xiang-guan#ru-he-cha-kan-zheng-shu-xu-lie-hao /// </summary> public static string serialNo => ""; /// <summary> /// key 私鑰 /// 私鑰和證書:https://wechatpay-api.gitbook.io/wechatpay-api-v3/ren-zheng/zheng-shu#sheng-ming-suo-shi-yong-de-zheng-shu /// </summary> public static string privateKey => FileHelper.ReadFile(Directory.GetCurrentDirectory()+"/UpFile/WeChatKey/key.txt", "utf-8"); } }
遠程請求微信接口的通用類:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; namespace WeChat.Api.Common { public class HttpHandler : DelegatingHandler { public HttpHandler() { InnerHandler = new HttpClientHandler(); } protected async override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { var auth = await BuildAuthAsync(request); string value = $"WECHATPAY2-SHA256-RSA2048 {auth}"; request.Headers.Add("Authorization", value); request.Headers.Add("Accept", "application/json"); request.Headers.Add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"); return await base.SendAsync(request, cancellationToken); } protected async Task<string> BuildAuthAsync(HttpRequestMessage request) { string method = request.Method.ToString(); string body = ""; if (method == "POST" || method == "PUT" || method == "PATCH") { var content = request.Content; body = await content.ReadAsStringAsync(); } string uri = request.RequestUri.PathAndQuery; var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds(); string nonce = Path.GetRandomFileName(); string message = $"{method}\n{uri}\n{timestamp}\n{nonce}\n{body}\n"; string signature = Sign(message); return $"mchid=\"{WeChatConfig.mchid}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{WeChatConfig.serialNo}\",signature=\"{signature}\""; } protected string Sign(string message) { // NOTE: 私鑰不包括私鑰文件起始的-----BEGIN PRIVATE KEY----- // 亦不包括結尾的-----END PRIVATE KEY----- string privateKey = WeChatConfig.privateKey; byte[] keyData = Convert.FromBase64String(privateKey); var rsa = RSA.Create(); rsa.ImportPkcs8PrivateKey(keyData, out _); byte[] data = System.Text.Encoding.UTF8.GetBytes(message); return Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)); } } }
請求參數的Model:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace WeChat.Api.Model.CreateOrder { public class PayOrder { /// <summary> /// 直連商戶申請的公眾號或移動應用appid。 /// </summary> public string appid { set; get; } /// <summary> /// 直連商戶的商戶號,由微信支付生成並下發。 /// </summary> public string mchid { set; get; } /// <summary> /// 商品描述 /// </summary> public string description { set; get; } /// <summary> /// 商戶系統內部訂單號,只能是數字、大小寫字母_-*且在同一個商戶號下唯一 /// </summary> public string out_trade_no { set; get; } /// <summary> /// 訂單支付回調接口 /// </summary> public string notify_url { set; get; } /// <summary> /// 訂單金額信息 /// </summary> public Amount amount { set; get; } } /// <summary> /// 微信支付金額實體 /// </summary> public class Amount { /// <summary> /// 訂單總金額,單位為分。 /// </summary> public int total { set; get; } /// <summary> /// 貨幣類型,CNY:人民幣,境內商戶號僅支持人民幣。 /// </summary> public string currency { set; get; } = "CNY"; } }
返回參數Model:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace WeChat.Api.Model.CreateOrder { public class ReturnPayOrder { /// <summary> /// 生成二維碼的鏈接 /// </summary> public string code_url { get; set; } } }
下單:
/// <summary> /// 統一下單接口 /// </summary> /// <param name="total">支付金額</param> /// <param name="OrderId">訂單ID</param> /// <param name="pixel">像素大小</param> [HttpPost("PayOrder")] public async Task<ReturnPayOrder> PayOrder(int total,string OrderId) { var orderNumber = $"{DateTime.Now:yyyyMMddHHmmssff}{CodeHelper.CreateNumCode(3)}"; HttpClient client = new HttpClient(new HttpHandler()); var req = new PayOrder { appid = WeChatConfig.appid, mchid =WeChatConfig.mchid, description = "nihao", out_trade_no = orderNumber, notify_url = "http://...../api/wechat/WxPayCallback",//這個是支付成功后的回調方法 amount = new Amount { total = total, currency = "CNY" } }; var bodyJson = new StringContent(req.ToJson(), Encoding.UTF8, "application/json"); var resp = await client.PostAsync("https://api.mch.weixin.qq.com/v3/pay/transactions/native", bodyJson); // 注意!!! 這個resp只是http的結果,需要把接口具體返回的值讀取出來,如果接口報錯的話,這地方可以看到具體的錯誤信息,我就是在這里入坑的。 var respStr = await resp.Content.ReadAsStringAsync(); var respos = respStr.ToObject<ReturnPayOrder>(); return respos; }
根據獲取的回調的url動態生成支付二維碼:
/// <summary> /// 動態生成二維碼(http://) /// </summary> /// <param name="url">下載路徑</param> /// <param name="pixel">像素大小</param> [HttpGet("GetQRCode")] public void GetQRCode(string url, int pixel) { Response.ContentType = "image/jpeg"; var bitmap = _qRCode.GetQRCode(url, pixel); MemoryStream ms = new MemoryStream(); bitmap.Save(ms, ImageFormat.Jpeg); Response.Body.WriteAsync(ms.GetBuffer(), 0, Convert.ToInt32(ms.Length)); Response.Body.Close(); }
支付成功回調方法:
/// <summary> /// 微信支付成功結果回調接口 /// </summary> /// <returns>退款通知http應答碼為200且返回狀態碼為SUCCESS才會當做商戶接收成功,否則會重試。注意:重試過多會導致微信支付端積壓過多通知而堵塞,影響其他正常通知。</returns> [HttpPost("WxPayCallback")] public async Task<ReturnNotifyModel> WxPayCallback() { FileHelper.AddLog("進入", "weixin1"); FileHelper.AddLog(HttpContext.Request.Path.ToString(), "weixin1"); try { using (StreamReader sr = new StreamReader(Request.Body, Encoding.UTF8)) { string strContent = sr.ReadToEndAsync().Result; FileHelper.AddLog(strContent, "weixin1"); var wxPayNotifyModel = strContent.ToObject<WxPayNotifyModel>(); var decryptStr = AesGcmHelper.AesGcmDecrypt(wxPayNotifyModel.resource.associated_data, wxPayNotifyModel.resource.nonce, wxPayNotifyModel.resource.ciphertext, "89chulmpeQlk568752345UIcd891512a"); return decryptStr.ToString().ToObject<ReturnNotifyModel>(); } } catch (Exception e) { FileHelper.AddLog("讀取異常", "weixin1"); throw; }
將這個api發布到服務器中,注意:回調方法一定是可以通過外網訪問的,並且還要在微信商戶平台中添加這個訪問地址,不然回調會失敗!
測試下單接口:
然后通過code_url動態生成支付二維碼:
然后將支付成功的回調方法發布到服務器上:
然后,通過api接口生成的二維碼模擬前端掃碼支付:
后台接口回調日志:
ciphertext字段解密后的數據:
源碼:鏈接: https://pan.baidu.com/s/17QaZnIRrDCUoO2x4RvzhIA 提取碼: zb44
微信支付這塊其實gitee上有好多不錯的開源的代碼,比如paylink ,盛派,人家已經封裝的很好了,但是對於初學者來說並不友好,還是自己根據官網一步一步的來清晰,不然過程雲里霧里的。所以我做微信支付都是自己根據官網然后借鑒網上的一些理論代碼,一步一步來實現的,這樣自己對整個過程就比較清楚,后續自己也可以封裝一下。