.net core 微信支付-微信小程序支付(服务端C#代码)
前言
前段时间研究了下微信支付-小程序支付的功能。但微信支付文档中关于.net C#的语言的sdk没有,只有java go 和php版本的,当然社区也有很多已经集成好的微信支付.net core sdk,比如盛派家的(Senparc.Weixin),DotNetCore.SKIT.FlurlHttpClient.Wechat , paylink等,但他们的框架又都太臃肿了(功能太多太强大集成度也复杂),我只需要个小程序微信支付即可,而且用他们的sdk还得先学习下他们的文档,拿来主义固然好,主要的是我希望更清晰的了解微信支付的流程和集成方式,使用他们的sdk等以后对微信支付流程有更清晰的了解后再用也不迟,所以打算自己实现。在此整理下以防自己忘了。
接入前准备这里就不提了,参考官方文档,一步一步来就可以了。
实现思路
java和C# 基本上差不多,照着Java的sdk来就可以了。
这里涉及到几个关键配置。
- 商户号 mchid
- 商户注册申请和商户apiv3证书(APIv3证书私钥) private.key 调用微信api时签名使用
- 商户apiv3证书密钥 merchant_serial_no
- 微信平台证书的公钥 public.key 验证微信响应时验签使用
- 小程序的 appid
- 对应小程序的支付用户的openid
小程序支付流程

发起支付
发起支付的文档参考小程序下单
这里的小程序下单并不会直接发起支付,而是生成一个预订单号,prepay_id,当有了这个id后然后再调用小程序发起支付接口进行实际支付。
以下是代码小程序下单的C#版
/// <summary> /// 下单对应的请求DTO /// </summary> public class WeiXinPayOrderRequestDTO { /// <summary> /// 应用id /// </summary> public string appid { get; set; } /// <summary> /// 商户id /// </summary> public string mchid { get; set; } /// <summary> /// 商品描述 /// </summary> public string description { get; set; } /// <summary> /// 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一。 /// </summary> public string out_trade_no { get; set; } /// <summary> /// 订单失效时间,遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE,yyyy-MM-DD表示年月日, /// T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区 /// (+08:00表示东八区时间,领先UTC8小时,即北京时间)。 /// 例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒 /// </summary> public string time_expire { get; set; } /// <summary> /// 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用,实际情况下只有支付完成状态才会返回该字段。 /// 本系统存放的是订单id /// </summary> public string attach { get; set; } /// <summary> /// 通知URL必须为直接可访问的URL,不允许携带查询串,要求必须为https地址。 /// </summary> public string notify_url { get; set; } /// <summary> /// 是否开通发票 /// </summary> public bool support_fapiao { get; private set; } = false; /// <summary> /// 订单金额 /// </summary> public WeiXinPayOrderAmout amount { get; set; } /// <summary> /// 支付人 /// </summary> public WeiXinPayOrderPayer payer { get; set; } /// <summary> /// 结算信息 /// </summary> public WeiXinPayOrderSettleInfo settle_info { get; private set; } = new WeiXinPayOrderSettleInfo { profit_sharing = false, }; } public class WeiXinPayOrderAmout { /// <summary> ///总金额 /// </summary> public int total { get; set; } /// <summary> /// CNY:人民币,境内商户号仅支持人民币。 /// </summary> public string currency { get; private set; } = "CNY"; } public class WeiXinPayOrderPayer { /// <summary> /// 小程序对应的用户openid /// </summary> public string openid { get; set; } } public class WeiXinPayOrderSettleInfo { /// <summary> /// 是否指定分账 /// </summary> public bool profit_sharing { get; set; } }
接下来是提交订单
public async Task<WeiXinPayOrderResponseDTO> SubmitOrderAsync(WeiXinPayOrderRequestDTO request) { string nonceStr = StringUtils.RandomNonceString(10); long timeStamp = DateTimeHelper.NowTimeStamp(); HttpRequestMessage httpRequest = new() { Method = HttpMethod.Post }; request.appid = options.AppId; request.mchid = options.Mchid; string json = request.ToJson(); httpRequest.Content = new StringContent(json, Encoding.UTF8, "application/json"); httpRequest.Headers.Add("Accept", "*/*"); httpRequest.Headers.Add("User-Agent", "dotnet/6.0"); httpRequest.RequestUri = new Uri("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"); string orderTosignData = BuildSubmitOrderToSignData(httpRequest.Method, httpRequest.RequestUri, timeStamp, nonceStr, json); string signature = ""; // 创建RSA加密服务提供程序实例 using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) { // 加载私钥 rsa.ImportFromPem(options.ApiPrivateKeyContent); // 将待签名的数据转换为字节数组 byte[] dataBytes = Encoding.UTF8.GetBytes(orderTosignData); // 计算SHA256 with RSA签名 byte[] signatureBytes = rsa.SignData(dataBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); // 将签名结果进行Base64编码 signature = Convert.ToBase64String(signatureBytes); // 输出签名值 Console.WriteLine("签名值: " + signature); } string authrizationValue = $"mchid=\"{options.Mchid}\",serial_no=\"{options.ApiSerialNumber}\",nonce_str=\"{nonceStr}\",timestamp=\"{timeStamp}\",signature=\"{signature}\""; httpRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("WECHATPAY2-SHA256-RSA2048", authrizationValue); HttpResponseMessage response = await httpClient.SendAsync(httpRequest); string responseString = await response.Content.ReadAsStringAsync(); Console.WriteLine($"Response: {responseString}"); var orderResult = Newtonsoft.Json.JsonConvert.DeserializeObject<WeiXinPayOrderResponseDTO>(responseString); return orderResult; }
然后是调用微信支付所需要的requestPayment body
public class WeiXinPayrequestPayment { /// <summary> /// 时间戳 /// </summary> public string timeStamp { get; set; } /// <summary> /// 随机字符 /// </summary> public string nonceStr { get; set; } /// <summary> /// 支付的预支付号 /// </summary> public string package { get; set; } /// <summary> /// 签名类型 /// </summary> public string signType { get; } = "RSA"; /// <summary> /// 小程序支付发起的签名 /// </summary> public string paySign { get; set; } } public async Task<WeiXinPayrequestPayment> WeiXinPayrequestPayment(string prepayId) { string nonceStr = StringUtils.RandomNonceString(16); string timeStamp = DateTimeHelper.NowTimeStamp().ToString(); string package = $"prepay_id={prepayId}"; string dataToSign = options.AppId + "\n" + timeStamp + "\n" + nonceStr + "\n" + package + "\n"; string sign = ""; // 创建RSA加密服务提供程序实例 using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) { // 加载私钥 rsa.ImportFromPem(options.ApiPrivateKeyContent); // 将待签名的数据转换为字节数组 byte[] dataBytes = Encoding.UTF8.GetBytes(dataToSign); // 计算SHA256 with RSA签名 byte[] signatureBytes = rsa.SignData(dataBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); // 将签名结果进行Base64编码 sign = Convert.ToBase64String(signatureBytes); } return new WeiXinPayrequestPayment { nonceStr = nonceStr, package = package, paySign = sign, timeStamp = timeStamp, }; }
微信异步通知的验签与解密
解密
微信异步通知的body中存在加密部分, 其中的body中ciphertext字段是密文,商户端需要使用apiv3的密钥进行解密,文档参见
如何加密解密敏感信息代码如下:
private async Task<string> WechatAesGcmDecrypt(string associatedData, string nonce, string ciphertext) { GcmBlockCipher gcmBlockCipher = new GcmBlockCipher(new AesEngine()); AeadParameters aeadParameters = new AeadParameters( new KeyParameter(Encoding.UTF8.GetBytes(options.ApiAESKey)),// 商户APIv3 密钥 128, Encoding.UTF8.GetBytes(nonce), Encoding.UTF8.GetBytes(associatedData)); gcmBlockCipher.Init(false, aeadParameters); byte[] data = Convert.FromBase64String(ciphertext); byte[] plaintext = new byte[gcmBlockCipher.GetOutputSize(data.Length)]; int length = gcmBlockCipher.ProcessBytes(data, 0, data.Length, plaintext, 0); gcmBlockCipher.DoFinal(plaintext, length); return Encoding.UTF8.GetString(plaintext); }
验签:
由于签名算法中,调用微信api后微信响应的数据(response)和微信主动请求商户地址的数据(wechat request)都是使用微信平台证书的私钥进行签名的。尤为注意,验签这里必须使用微信平台证书中的公钥public_key进行验签操作。
public async Task<bool> VerifyWeiXinSign(string nonce, long timestamp, string serial, string body, string signature) { logger.LogInformation($"开始验证微信签名,\n nonce:{nonce}" + $"\n timestamp:{timestamp}" + $"\n serial:{serial}\n body:{body}\n signature:{signature}"); try { // 将Unix时间戳转换为DateTime DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(timestamp).ToLocalTime(); // 获取当前时间 DateTime currentTime = DateTime.Now; // 计算时间差 TimeSpan timeDifference = currentTime.Subtract(dateTime); if (timeDifference.Minutes > 20) { throw new Exception("当前请求已过期"); } string dataToSign = $"{timestamp}\n{nonce}\n{body}\n"; // 待验签的数据 byte[] dataBytes = Encoding.UTF8.GetBytes(dataToSign); using var reader = new StringReader(options.WechatPlatformPublicKey); var pemReader = new PemReader(reader); object obj = pemReader.ReadObject(); RsaKeyParameters publicKeyParams = null; if (obj is RsaKeyParameters) { publicKeyParams = (RsaKeyParameters)obj; } else { throw new Exception("公钥内容加载失败,Invalid public key format."); } // 签名 byte[] signatureBytes = Convert.FromBase64String(signature); var verifier = SignerUtilities.GetSigner("SHA256withRSA"); verifier.Init(false, publicKeyParams); verifier.BlockUpdate(dataBytes, 0, dataBytes.Length); bool isVaild = verifier.VerifySignature(signatureBytes); logger.LogInformation($"微信验证结果 nonce:{nonce} isVaild:{isVaild}"); return isVaild; } catch (Exception ex) { throw; } finally { logger.LogInformation($"结束验证微信签名"); } }
注意事项
- 第三方(商户)调用微信api的签名中使用的密钥 都是商户apiv3证书的私钥进行签名的也就是private_key。
- 在微信通知到第三方的通知url上或者调用完微信接口后的Response,必须使用微信平台证书的公钥进行验签的,也就是publick_key。
- 由于微信调整,微信平台证书可能会定期更换,这里需要定期更新微信平台证书的公钥。具体参考平台证书更换指引由于我只处于研究阶段,所以这里的微信平台证书自动更换就没往下研究。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· [翻译] 为什么 Tracebit 用 C# 开发
· 腾讯ima接入deepseek-r1,借用别人脑子用用成真了~
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· DeepSeek崛起:程序员“饭碗”被抢,还是职业进化新起点?
· RFID实践——.NET IoT程序读取高频RFID卡/标签