你填写的是公众号不是服务号
小程序申请微信支付功能申请?小程序AppID:wx18cc89ccba039ccc 经营场景中需要填写“服务号或公众号AppID,我填写了公众号AppID(wx35fbdc2ce47a7a63),但是提示"appid需是已认证的服务号、政府或媒体类型的订阅号"。微信公众号我已经认证过了。是在哪个环节出问题了, [图片]
7小时前找你支付服务提供方
微信小程序已受控,请发起延时交易,请问如何解决此问题?我们公司的小程序【初芯科技】,ID:wx39a3aeb6f1cd281f,在发起订餐,在支付订单金额的时候,提示:微信小程序已受控,请发起延时交易,请问如何解决此问题?[图片]
7小时前可以参考https://developers.weixin.qq.com/miniprogram/product/jiaoyilei/yunyingguifan.html#%E5%AE%9E%E7%89%A9%E7%94%B5%E5%95%86%E7%B1%BB%E5%B0%8F%E7%A8%8B%E5%BA%8F%E8%BF%90%E8%90%A5%E8%A7%84%E8%8C%83
对接支付服务商提示”该商户号类型暂不支持与该AppID关联“?对接支付服务商提示该商户号类型暂不支持与该AppID关联,服务商那边说是因为”间联商户不支持关联商家自营类小程序“,这个商家自营类小程序怎么判断呢,可以在哪里进行更改吗
7小时前免开发可以使用代金券,商家券设计之初就是为了让商户开发集成的
微信支付商家券功能当前暂时仅提供API接口功能,官方同学好久为无开发能力的商家提供配置商家券???微信支付商家券功能当前暂时仅提供API接口功能,有开发意愿的服务商或者商户可使用API接口完成商家券创建、发放、领取、核销、数据对账等全链路操作。未来微信支付将会为无开发意愿的商家提供服务商平台页面配置商家券以及对应管理功能。 [图片]
8小时前1、不影响 2、不支持主动开通
服务商商户跟小程序绑定的时候需要关闭发货信息管理功能,会影响分账吗?服务商商户下面开通了平台收付通,在跟小程序绑定的时候提示,暂不支持该类型商户号绑定本AppID,请点击“拒绝”结束该流程,,与客服沟通需要关闭发货信息管理功能,关闭发货信息管理功能就会影响后续小程序的跳转。 所以有以下几个问题 1、关闭发货信息管理功能之后会影响分账吗? 2、如果关闭发货信息管理之后,如何重新申请开通?
8小时前APPID被管控以后不支持绑定服务商商户号
通过支付服务商接入微信支付配置appid时提示”该商户号类型暂不支持与该appid关联“通过支付服务商接入微信支付配置appid时提示”该商户号类型暂不支持与该appid关联“,我们公司之前的类目是商家自营类目的后面更改为电商平台之后还是无法配置关联这个是为什么呢
8小时前一个公司可以申请5个商户号
微信商家号 公司认证 一个微信号可以认证多少商家号?微信商家号 公司认证 一个微信号可以认证多少商家号? 怎么查询到的?
9小时前小程序不需要配置jsapi支付目录
不同商户号jsapi授权目录用同一个域名,这个上限是多少?同时给多家企业开发独立小程序,后端数据库以及域名是同一套,然后每个小程序微信支付商户号设置的jsapi授权目录都是同一个,这个是否有上限?
22小时前看sdk readme [图片]
Message: 无可用的平台证书,请在商户平台-API安全申请使用微信支付公钥。(具体要怎么弄?)Message: 无可用的平台证书,请在商户平台-API安全申请使用微信支付公钥。可查看指引https://pay.weixin.qq.com/docs/merchant/products/platform-certificate/wxp-pub-key-guide.html 请问这个要怎么具体操作,有没有示例代码? 因为这个错是根据微信支付的go sdk(https://pay.weixin.qq.com/doc/v3/merchant/4012076515)做遇到的,那看来sdk给的例子用的是平台公钥,需要换成微信支付公钥。 但是微信支付公钥的具体代码怎么写?有完整的sdk调用方法吗?这个指引文档https://pay.weixin.qq.com/docs/merchant/products/platform-certificate/wxp-pub-key-guide.html 里只有理论。 还是说这个没有办法用sdk,得自己研究非对称算法?
1天前你的associated_data为什么没有值呢?
微信支付v3 jsapi 在.net8 环境下不能解密支付后的通知报文,如何解决?我正在开发一个微信里H5支付功能,目前被不能解密通知报文折磨1个星期了,求支援, 我的代码在最下面 运行到这一步报错 byte[] combinedCiphertext = new byte[ciphertextBytes.Length + tag.Length]; Buffer.BlockCopy(ciphertextBytes, 0, combinedCiphertext, 0, ciphertextBytes.Length); Buffer.BlockCopy(tag, 0, combinedCiphertext, ciphertextBytes.Length, tag.Length); 报错和日志如下, 2025-06-22 10:13:44.934 +08:00 [INF] APIv3密钥指纹: F6EE716DF91A4CAC 2025-06-22 10:13:44.936 +08:00 [INF] 分离后的密文长度: 423, 标签: 1429155F665DC3D543A1FE8DB52B0661 2025-06-22 10:13:44.936 +08:00 [INF] 关联数据为 null,使用空字节数组 2025-06-22 10:13:44.948 +08:00 [INF] 解密前的参数 - Key: 3931333239353433303130383539343830343139383938343533373730393738, Nonce: k2Nhn3Yjg2cv, AD: , , Tag: 1429155F665DC3D543A1FE8DB52B0661 2025-06-22 10:13:50.156 +08:00 [ERR] ---------- RemoteServiceErrorInfo ---------- { "code": null, "message": "对不起,在处理您的请求期间产生了一个服务器内部错误!!", "details": null, "data": {}, "validationErrors": null } 2025-06-22 10:13:50.157 +08:00 [ERR] mac check in GCM failed Org.BouncyCastle.Crypto.InvalidCipherTextException: mac check in GCM failed at Org.BouncyCastle.Crypto.Modes.GcmBlockCipher.DoFinal(Span`1 output) at Org.BouncyCastle.Crypto.Modes.GcmBlockCipher.DoFinal(Byte[] output, Int32 outOff) at LaborMall.WeChatPay.WeChatPayV3Service.DecryptWithDotNet(Byte[] key, Byte[] nonce, Byte[] ciphertextBytes, Byte[] tag, Byte[] associatedData) in E:\BSApp\mall\labormall-master\dev\aspnet-core\src\LaborMall.Application\WeChatPay\WeChatPayV3Service.cs:line 497 at LaborMall.WeChatPay.WeChatPayV3Service.AesGcmDecrypt(String associatedData, String nonce, String ciphertext) in E:\BSApp\mall\labormall-master\dev\aspnet-core\src\LaborMall.Application\WeChatPay\WeChatPayV3Service.cs:line 467 at LaborMall.WeChatPay.WeChatPayV3Service.Decrypt(WeChatPayEncryptedData encryptedData) in E:\BSApp\mall\labormall-master\dev\aspnet-core\src\LaborMall.Application\WeChatPay\WeChatPayV3Service.cs:line 362 at LaborMall.WeChatPay.WeChatPayV3Service.GetNonce() in E:\BSApp\mall\labormall-master\dev\aspnet-core\src\LaborMall.Application\WeChatPay\WeChatPayV3Service.cs:line 272 at LaborMall.Communication.Controllers.WeChatPayController.PayNotification2Async(String input) in E:\BSApp\mall\labormall-master\dev\aspnet-core\hosts\LaborMall.Communication.Host\Controllers\WeChatPayController.cs:line 133 at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) 以下是源码 using System; using System.Collections.Generic; using System.Net.Http; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using System.Web; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using LaborMall.WeChatPay.Dtos; using Aliyun.Credentials.Http; using System.IO; using Volo.Abp; using LaborMall.Data; using LaborMall.Favorites; using Microsoft.Extensions.Logging; using static System.Runtime.InteropServices.JavaScript.JSType; using Quartz.Logging; using Microsoft.IdentityModel.Tokens; using System.Text.RegularExpressions; using System.Text.Unicode; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Modes; using Org.BouncyCastle.Crypto.Engines; using Quartz.Util; using Org.BouncyCastle.Utilities; using System.Net; using Org.BouncyCastle.Crypto; namespace LaborMall.WeChatPay { public class WeChatPayV3Service { private readonly string _apiUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"; private readonly string _oauthUrl = "https://api.weixin.qq.com/sns/oauth2/access_token"; private readonly string _mchId; // 商户号 private readonly string _appId; // 公众号AppId private readonly string _appSecret; // 公众号AppSecret private readonly string _apiV3Key; // APIv3密钥 private readonly ICertificateService _certificateService; private readonly string _serialNo; // 商户证书序列号 private readonly HttpClient _httpClient; private readonly ILogger<WeChatPayV3Service> _logger; public WeChatPayV3Service( string mchId, string appId, string appSecret, string apiV3Key, ICertificateService certificateService, // 先注入证书服务 string serialNo, ILogger<WeChatPayV3Service> logger, HttpClient httpClient = null) { _mchId = mchId; _appId = appId; _appSecret = appSecret; _apiV3Key = apiV3Key; _certificateService = certificateService; _serialNo = serialNo; _httpClient = httpClient ?? new HttpClient(); _logger = logger; } /// <summary> /// 通过code获取用户OpenId /// </summary> public async Task<string> GetUserOpenIdAsync(string code) { if (string.IsNullOrEmpty(code)) { throw new ArgumentException("授权码不能为空"); } var url = $"{_oauthUrl}?appid={_appId}&secret={_appSecret}&code={HttpUtility.UrlEncode(code)}&grant_type=authorization_code"; var response = await _httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); var tokenResponse = JObject.Parse(content); var openId = tokenResponse["openid"]?.ToString(); if (string.IsNullOrEmpty(openId)) { throw new Exception("获取OpenId失败"); } return openId; } /// <summary> /// JSAPI下单 /// </summary> /// <summary> /// JSAPI下单 /// </summary> public async Task<string> CreateJsApiOrderAsync( string openId, string outTradeNo, string description, int totalAmount, string notifyUrl, string attach = null) { try { // 1. 构建请求体 var body = new { appid = _appId, mchid = _mchId, description = description, out_trade_no = outTradeNo, notify_url = notifyUrl, amount = new { total = totalAmount, currency = "CNY" }, payer = new { openid = openId }, attach = attach, time_expire = DateTime.UtcNow.AddMinutes(30).ToString("yyyy-MM-ddTHH:mm:sszzz") }; string bodyJson = JsonConvert.SerializeObject(body); _logger.LogInformation($"请求体JSON: {bodyJson}"); // 2. 生成签名并发送请求 var request = new HttpRequestMessage(HttpMethod.Post, _apiUrl); request.Content = new StringContent(bodyJson, Encoding.UTF8, "application/json"); string timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); string nonce = Guid.NewGuid().ToString("N"); _logger.LogInformation($"时间戳: {timestamp}"); _logger.LogInformation($"随机数: {nonce}"); // 使用新的API请求签名方法 string signature = GenerateApiRequestSignature(request.Method.Method, _apiUrl, timestamp, nonce, bodyJson); string authHeader = GetAuthHeader(timestamp, nonce, signature); request.Headers.Add("Authorization", $"WECHATPAY2-SHA256-RSA2048 {authHeader}"); request.Headers.Add("Accept", "application/json"); request.Headers.Add("Wechatpay-Serial", _serialNo); request.Headers.Add("User-Agent", "LaborMallApp/1.0"); // 记录完整的请求头信息 _logger.LogInformation("请求头信息:"); foreach (var header in request.Headers) { _logger.LogInformation($"{header.Key}: {string.Join(", ", header.Value)}"); } // 3. 发送请求并解析响应 var response = await _httpClient.SendAsync(request); string responseJson = await response.Content.ReadAsStringAsync(); _logger.LogInformation($"响应状态码: {response.StatusCode}"); _logger.LogInformation($"响应内容: {responseJson}"); if (!response.IsSuccessStatusCode) throw new Exception($"微信支付请求失败: {response.StatusCode}\n{responseJson}"); dynamic result = JsonConvert.DeserializeObject(responseJson); return result.prepay_id; } catch (Exception ex) { _logger.LogError(ex, "JSAPI下单过程中发生错误"); throw; } } /// <summary> /// 生成前端调起支付参数 /// </summary> public Dictionary<string, string> GetJsApiSdkParams(string prepayId) { string timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); string nonceStr = Guid.NewGuid().ToString("N"); string package = $"prepay_id={prepayId}"; string message = $"{_appId}\n{timestamp}\n{nonceStr}\n{package}\n"; string paySign = GenerateSignature(message); // 修改后的签名方法 return new Dictionary<string, string> { { "appId", _appId }, { "timeStamp", timestamp }, { "nonceStr", nonceStr }, { "package", package }, { "signType", "RSA" }, { "paySign", paySign } }; } /// <summary> /// 生成API请求签名 (用于微信支付V3 API请求) /// </summary> private string GenerateApiRequestSignature(string method, string url, string timestamp, string nonce, string body) { try { // 规范化URL路径 var uri = new Uri(url); string path = uri.AbsolutePath; // 构造规范化的签名消息 string message = $"{method.ToUpperInvariant()}\n{path}\n{timestamp}\n{nonce}\n{body}\n"; _logger.LogInformation($"生成API请求签名的消息内容:\n{message}"); return GenerateSignature(message); } catch (Exception ex) { _logger.LogError(ex, "生成API请求签名时发生错误"); throw; } } /// <summary> /// 基础签名方法 (实际执行RSA签名) /// </summary> private string GenerateSignature(string message) { using var rsa = _certificateService.LoadPrivateKey(); byte[] data = Encoding.UTF8.GetBytes(message); byte[] signature = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); string base64Sign = Convert.ToBase64String(signature); _logger.LogInformation($"生成的签名: {base64Sign}"); return base64Sign; } private bool VerifySignature(string message, string signature, string privateKey) { using (var rsa = RSA.Create()) { rsa.ImportFromPem(privateKey); return rsa.VerifyData( Encoding.UTF8.GetBytes(message), Convert.FromBase64String(signature), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); } } private string GetAuthHeader(string timestamp, string nonce, string signature) { // 确保所有值都是ASCII编码 var authValues = new Dictionary<string, string> { { "mchid", _mchId }, { "nonce_str", nonce }, { "timestamp", timestamp }, { "serial_no", _serialNo }, { "signature", signature } }; var sb = new StringBuilder(); foreach (var kv in authValues) { if (sb.Length > 0) sb.Append(","); sb.Append($"{kv.Key}=\"{kv.Value}\""); } return sb.ToString(); } //////////////////////////////////////////////////////////////通知处理部分、、、、、、、、、、、、、、、、、、、 public void GetNonce() { // 测试 // var decryptor = new WeChatPayV3Decryptor("你的真实APIv3密钥", logger); // { "id":"fc46e8ac-c0b6-5397-93f8-c857106144f0","create_time":null,"event_type":null,"resource_type":null,"resource":{ "original_type":null,"algorithm":"AEAD_AES_256_GCM","ciphertext":"INPe4z+FBIOoIatLGoNMmVtdrD8GD/Ic9u/mrzgyjNcEgH81EXl690d5XqeZlWRc0kORrWJdMWeF0C3ir7Farg9MsR1FRmZv+ULFsEtbKkzTVfn87gBqFLNauJIVj1uAwSqPF9wHCt3NdB+71i8Jpb6Fa8Lovk/n1KDCd5oDyB7d5NZ7Pf3+Z5lBM64I7aiaoiRPE9dmkl9odTpM6yXdOa7iDaGSe7pqob4y0btE+siRqrH9lnfEqHv2rnxuA7+Ma1I/gd8lHCnNjSwPOCXF1N0V/TVbvg2wH4yIldvhafkWwi+FVstfl9APQj+2HRTk705ULnMcs7Hn3ySL3utE6fJtQnUgJPEG8x397ZjcSSQtO8+r+rJdOuevx7FKHAzeOP0ny/0fD45Eig07LoGnlwbPmDugaKc5U080XFbploOGxLoTW8jFpFAQYBJLldmLjMpX30GzyqTZxx9T6LlumO/vc0Cy6ebUrz7iOYjMmAIruFkJGvj4M+VgJWk95JUnwNJZHKbBDT+Gu4QaS/OA9/ZjJM+Fq2VPZhLyYlc0uP7svCIwMmz7R6iKb+zevUarst1aGRMTFA==","nonce":"sb51ZQpI9N43","associated_data":null},"summary":"支付成功"} // 处理回调 var decryptedData = Decrypt(new WeChatPayEncryptedData { Algorithm = "AEAD_AES_256_GCM", Ciphertext = "bDEfmqkHlnR/h9GoUXmNtrdOZYse/hoY0EezSkx+esohSrm55riylrQIWaANNPTWk/bvEkIH9Gt7I0U3LTDfKjnVt3Y0MfAups1WwQF4dBB9juOCwUHLoYS4kpI8FOq0UAx+OIkEOC4e21uKdRlkxi9Y6V6jxgCEpz53+4pZ64CeCFboPcBxyQFOjd0dUqK3T4X2unBIuyXbs3FbjWDyRbashR7diXhjzUbO8OWrM/lN5VBvUGXtJb2mYRy4OOKC1Z0mL8t1zCecvx3wqc0xUVu4L0TI5Af2ma/4Jiz09v5dBhjiduOFClGPPgBKMnpyNnzEMldLVsrYnPxjsQkPW+2NCel5UMEz/W/JfD+MidlcUM9UTzMPZh1ZMkAQnjIDn2mzHh0dhLQF7y998ZHwccsMOo5bSvYTPSGDjyy/5S6MczUpjaZRlaDs0Hyz/Mzmaq2H7XSzGRWdp3/Hdb3p/VNLYvHvHQnsMWKGRq7LUB+GnLaHSSXiDEg0p8irl+XkcpVO007MrPOnbv6K7uGYaAVIBs2urJKA7H7ul45Fa2UnpwydhEEKFCkVX2Zdw9VDof6NtSsGYQ==", Nonce = "k2Nhn3Yjg2cv", AssociatedData = null }); // 处理业务逻辑 Console.WriteLine($"订单号: {decryptedData.OutTradeNo}, 金额: {decryptedData.Amount.Total}"); } /// <summary> /// 处理微信支付通知 /// </summary> [RemoteService(false)] public async Task<AppResult<string>> HandlePaymentNotificationAsync(WeChatPayNotifyRequestDto input) { try { // 1. 记录原始回调数据 string rawJson = JsonConvert.SerializeObject(input); _logger.LogInformation($"收到支付回调: {rawJson}"); // 2. 验证数据结构 if (input?.Resource == null) throw new ArgumentNullException("Resource"); // 3. 解密数据 WeChatPayDecryptedDataDto decryptedJson = Decrypt(input.Resource); // 4. 验证解密结果 if (string.IsNullOrEmpty(decryptedJson.OutTradeNo)) throw new Exception("解密结果为空"); // 5. 返回成功响应 return new AppResult<string> { Success = true, Data = decryptedJson.OutTradeNo, }; } catch (Exception ex) { _logger.LogError(ex, "处理支付回调失败"); return new AppResult<string> { Success = false, Message = ex.Message }; } } private async Task ProcessPaymentResultAsync(WeChatPayDecryptedDataDto data) { // 根据支付结果更新业务状态 // 例如:更新订单状态为已支付 _logger.LogInformation($"处理微信支付结果: 订单号={data.OutTradeNo}, 交易状态={data.TradeState}"); // 这里可以添加你的业务逻辑 // 例如:var order = await _orderRepository.FindAsync(x => x.OrderNo == data.OutTradeNo); // if (order != null) { ... } } /// <summary> /// 解密微信支付回调数据(最终版) /// </summary> public WeChatPayDecryptedDataDto Decrypt(WeChatPayEncryptedData encryptedData) { // encryptedData.AssociatedData = encryptedData.AssociatedData ?? "resource_type=encrypt-resource"; // 1. 验证并修复输入数据 if (encryptedData == null) throw new ArgumentNullException(nameof(encryptedData)); //var fixedData = ValidateAndFixEncryptedData(encryptedData); // _logger.LogInformation($"解密参数 - Nonce: {fixedData.Nonce}, AssociatedData: {fixedData.AssociatedData}, Ciphertext长度: {fixedData.Ciphertext.Length}"); string decryptedJson = AesGcmDecrypt(encryptedData.AssociatedData, encryptedData.Nonce, encryptedData.Ciphertext); // 6. 解析结果 _logger.LogDebug($"解密成功,原始JSON: {decryptedJson}"); return JsonConvert.DeserializeObject<WeChatPayDecryptedDataDto>(decryptedJson); } /// <summary> /// 证书/回调报文解密 /// </summary> /// <param name="associatedData"></param> /// <param name="nonce"></param> /// <param name="ciphertext"></param> /// <returns></returns> public string AesGcmDecrypt(string associatedData, string nonce, string ciphertext) { // 1. 严格验证密钥格式 if (string.IsNullOrEmpty(_apiV3Key)) throw new ArgumentNullException(nameof(_apiV3Key)); if (_apiV3Key.Length != 32) throw new ArgumentException($"APIv3密钥长度必须为32字节, 当前长度: {_apiV3Key.Length}"); // 3. 记录密钥指纹用于验证 using (var sha256 = SHA256.Create()) { byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(_apiV3Key)); string keyFingerprint = BitConverter.ToString(hash).Replace("-", "").Substring(0, 16); _logger.LogInformation($"APIv3密钥指纹: {keyFingerprint}"); } // 4. 增强Base64处理 string sanitizedCiphertext = ciphertext; // 4.1 移除所有非Base64字符 sanitizedCiphertext = Regex.Replace(sanitizedCiphertext, @"[^a-zA-Z0-9\+/\-_=]", ""); // 4.2 处理URL编码字符 if (sanitizedCiphertext.Contains('%')) { try { sanitizedCiphertext = WebUtility.UrlDecode(sanitizedCiphertext); _logger.LogInformation("URL解码后: " + sanitizedCiphertext); } catch (Exception decodeEx) { _logger.LogWarning($"URL解码失败: {decodeEx.Message}"); } } // 4.3 标准化Base64字符 sanitizedCiphertext = sanitizedCiphertext .Replace('_', '/') .Replace('-', '+'); // 4.4 添加Base64填充 int mod4 = sanitizedCiphertext.Length % 4; if (mod4 > 0) sanitizedCiphertext += new string('=', 4 - mod4); // 5. 尝试Base64解码 byte[] ciphertextBytes; ciphertextBytes = Convert.FromBase64String(sanitizedCiphertext); // 6. 分离加密数据和认证标签 if (ciphertextBytes.Length < 16) throw new ArgumentException("密文太短,无法包含认证标签"); // 最后16字节是认证标签 int ciphertextLength = ciphertextBytes.Length - 16; byte[] tag = new byte[16]; byte[] actualCiphertext = new byte[ciphertextLength]; // 复制前 N-16 字节为实际密文 Buffer.BlockCopy(ciphertextBytes, 0, actualCiphertext, 0, ciphertextLength); // 复制最后16字节为标签 Buffer.BlockCopy(ciphertextBytes, ciphertextLength, tag, 0, 16); _logger.LogInformation($"分离后的密文长度: {actualCiphertext.Length}, 标签: {BitConverter.ToString(tag).Replace("-", "")}"); // 7. 准备参数 byte[] key = Encoding.UTF8.GetBytes(_apiV3Key); byte[] nonceBytes = Encoding.UTF8.GetBytes(nonce); //byte[] adBytes = string.IsNullOrEmpty(associatedData) ? new byte[0] : Encoding.UTF8.GetBytes(associatedData ?? ""); // 处理关联数据:区分 null 和空字符串 byte[] adBytes; if (associatedData == null) { _logger.LogInformation("关联数据为 null,使用空字节数组"); adBytes = Array.Empty<byte>(); } else { adBytes = Encoding.UTF8.GetBytes(associatedData); } // 8. 使用.NET原生库解密 return DecryptWithDotNet(key, nonceBytes, ciphertextBytes, tag, adBytes); } /// <summary> /// 使用.NET原生库进行解密 /// </summary> private string DecryptWithDotNet(byte[] key, byte[] nonce, byte[] ciphertextBytes, byte[] tag, byte[] associatedData) { try { // byte[] plaintext = new byte[ciphertext.Length]; _logger.LogInformation($"解密前的参数 - Key: {BitConverter.ToString(key).Replace("-", "")}, Nonce: {Encoding.UTF8.GetString(nonce)}, AD: {Encoding.UTF8.GetString(associatedData)}, , Tag: {BitConverter.ToString(tag).Replace("-", "")}"); GcmBlockCipher gcmBlockCipher = new(new AesEngine()); AeadParameters aeadParameters = new( new KeyParameter(Encoding.UTF8.GetBytes(_apiV3Key)), 128, nonce, //Encoding.UTF8.GetBytes(nonce), associatedData);// Encoding.UTF8.GetBytes(associatedData)); gcmBlockCipher.Init(false, aeadParameters); // 2. 合并密文和认证标签(BouncyCastle要求) byte[] combinedCiphertext = new byte[ciphertextBytes.Length + tag.Length]; Buffer.BlockCopy(ciphertextBytes, 0, combinedCiphertext, 0, ciphertextBytes.Length); Buffer.BlockCopy(tag, 0, combinedCiphertext, ciphertextBytes.Length, tag.Length); // 3. 执行解密 byte[] plaintext = new byte[gcmBlockCipher.GetOutputSize(combinedCiphertext.Length)]; int len = gcmBlockCipher.ProcessBytes(combinedCiphertext, 0, combinedCiphertext.Length, plaintext, 0); gcmBlockCipher.DoFinal(plaintext, len); _logger.LogInformation($"解密成功,明文: {Encoding.UTF8.GetString(plaintext)}"); return Encoding.UTF8.GetString(plaintext); } catch (AuthenticationTagMismatchException ex) { // 详细记录错误信息 _logger.LogError($"认证标签不匹配: " + $"Key: {BitConverter.ToString(key).Replace("-", "").Substring(0, 8)}..., " + $"Nonce: {Encoding.UTF8.GetString(nonce)}, " + $"AD: {Encoding.UTF8.GetString(associatedData)}, " + $"Ciphertext length: {ciphertextBytes.Length}, " + $"Tag: {BitConverter.ToString(tag).Replace("-", "")}"); throw new WeChatPayDecryptionException("解密失败: 认证标签不匹配", ex); } } } public class WeChatPayDecryptionException : Exception { public WeChatPayDecryptionException(string message, Exception innerException) : base(message, innerException) { } } }
1天前