c# .net 微信支付v3,支付结果通知处理;含AEAD_AES_256_GCM解码
作者的程序框架:.NET Framework 4.6.1
微信官方说明地址: 注意重点:
微信支付-开发者文档
https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_5.shtml
注意: • 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。 • 如果在所有通知频率后没有收到微信侧回调,商户应调用查询订单接口确认订单状态。 特别提醒:商户系统对于开启结果通知的内容一定要做签名验证,并校验通知的信息是否与商户侧的信息一致,防止数据泄露导致出现“假通知”,造成资金损失。参数解密
下面详细描述对通知数据进行解密的流程: 1、用商户平台上设置的APIv3密钥【微信商户平台—>账户设置—>API安全—>设置APIv3密钥】,记为key; 2、针对resource.algorithm中描述的算法(目前为AEAD_AES_256_GCM),取得对应的参数nonce和associated_data; 3、使用key、nonce和associated_data,对数据密文resource.ciphertext进行解密,得到JSON形式的资源对象; 注: AEAD_AES_256_GCM算法的接口细节,请参考rfc5116。微信支付使用的密钥key长度为32个字节,随机串nonce长度12个字节,associated_data长度小于16个字节并可能为空字符串。证书和回调报文解密
微信支付-开发者文档
https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_2.shtml
位置:文档中心>开发指南>证书和回调报文解密
官方提供的Net代码public class AesGcm
{
private static string ALGORITHM = "AES/GCM/NoPadding";
private static int TAG_LENGTH_BIT = 128;
private static int NONCE_LENGTH_BYTE = 12;
private static string AES_KEY = "yourkeyhere";
public static string AesGcmDecrypt(string associatedData, string nonce, string ciphertext)
{
GcmBlockCipher gcmBlockCipher = new GcmBlockCipher(new AesEngine());
AeadParameters aeadParameters = new AeadParameters(
new KeyParameter(Encoding.UTF8.GetBytes(AES_KEY)),
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);
}
}
给项目添加引用包这里有个坑,如果你直接用是会报错的
所以需要引用一个包
项目》引用》右键“NuGet”》搜索“Portable.BouncyCastle”》找到如图的包安装即可
我安装版本数据
描述:BouncyCastle portable version with support for .NET 4, .NET Standard 2.0
版本:1.9.0
作者:Claire Novotny
发布日期:2021年10月19日 (2021/10/19)
项目URL:https://www.bouncycastle.org/csharp/
完整代码 AEAD_AES_256_GCM 解码工具类using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace test1.WeiXinPay.Tools
{
///
/// 解密微信通知结果帮助类
/// 参考资料:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_2.shtml
/// .NET5环境使用该代码,需要安装Portable.BouncyCastle组件
///
public class AesGcmHelper
{
private static string ALGORITHM = "AES/GCM/NoPadding";
private static int TAG_LENGTH_BIT = 128;
private static int NONCE_LENGTH_BYTE = 12;
private static string AES_KEY = string.Empty;
///
/// 解密微信通知结果帮助类
///
/// 通知数据resource下的associated_data
/// 通知数据resource.nonce
/// 通知数据resource.ciphertext
/// APIV3的密钥key
///
public static string AesGcmDecrypt(string associatedData, string nonce, string ciphertext, string APIV3Key)
{
GcmBlockCipher gcmBlockCipher = new GcmBlockCipher(new AesEngine());
AeadParameters aeadParameters = new AeadParameters(
new KeyParameter(Encoding.UTF8.GetBytes(APIV3Key)),
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);
}
}
}
所需模型代码
///回调页面-业务代码/// 微信支付通过后的回调 模型 /// public class NotifyUrlModel { ////// 通知的唯一ID /// public string id { get; set; } ////// 通知创建的时间,遵循rfc3339标准格式, /// public string create_time { get; set; } ////// 通知的资源数据类型,支付成功通知为encrypt-resource /// public string resource_type { get; set; } ////// 通知的类型,支付成功通知的类型为TRANSACTION.SUCCESS /// public string event_type { get; set; } ////// 回调摘要:如:支付成功 /// public string summary { get; set; } ////// 通知资源数据 json格式, /// public ResourceModel resource { get; set; } } ////// 通知资源数据 json格式, /// public class ResourceModel { ////// 原始回调类型,为transaction /// public string original_type { get; set; } ////// 对开启结果数据进行加密的加密算法,目前只支持AEAD_AES_256_GCM /// public string algorithm { get; set; } ////// Base64编码后的开启/停用结果数据密文 /// public string ciphertext { get; set; } ////// 附加数据 /// public string associated_data { get; set; } ////// 加密使用的随机串 /// public string nonce { get; set; } }
////// 订单回调地址 /// ///[HttpPost] public JsonResult NotifyUrl() { var result = new { code= "SUCCESS" , message = "成功" }; var httpContext= HttpContext; var request = Request; try { #region 获取字符串流 System.IO.Stream s = request.InputStream; int count = 0; byte[] buffer = new byte[1024]; StringBuilder builder = new StringBuilder(); while ((count = s.Read(buffer, 0, 1024)) > 0) { builder.Append(Encoding.UTF8.GetString(buffer, 0, count)); } s.Flush(); s.Close(); s.Dispose(); var str = Encoding.UTF8.GetString(buffer); #endregion #region 获取请求头信息 StringBuilder RHeadersStr = new StringBuilder(); RHeadersStr.Append("{"); foreach (var item in request.Headers) { RHeadersStr.Append($""{item.ToString()}":"{request.Headers[item.ToString()]}","); } RHeadersStr.Remove((RHeadersStr.Length - 1), 1); RHeadersStr.Append("}"); #endregion #region 把字符流和请求头的信息写入日志 log.Info($@" 获取字符串流:{str.Trim(' ')}rn 获取请求头信息:{RHeadersStr.ToString().Trim()}rn ==================================================rn "); #endregion #region 获取字符串流 string contentStr = str.Trim(' '); #endregion #region 获取请求头信息 string headersStr = RHeadersStr.ToString().Trim(); #endregion NotifyUrlModel notifyUrlModel = JsonConvert.DeserializeObject (contentStr); JObject headers = JsonConvert.DeserializeObject (contentStr); string APIV3Key = "XXXXXadsadsadasddddAPIV3Key"; //获取解码数据 var decryptStr = AesGcmHelper.AesGcmDecrypt(notifyUrlModel.resource.associated_data, notifyUrlModel.resource.nonce, notifyUrlModel.resource.ciphertext, APIV3Key); } catch (Exception e) { log.Error(e.LogErrorTxt()); throw; } return Json(result); }



