{"record":{"id":"309dbd4cc485a308","repo":"JeffreySu/WeiXinMPSDK","slug":"brandapicredentials-tenpaynotifyhandler","errorCode":null,"errorMessage":"brandApiCredentials","messagePattern":"brandApiCredentials","errorType":"exception","errorClass":"ArgumentNullException","httpStatus":null,"severity":"error","filePath":"src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/HttpHandlers/TenPayNotifyHandler.cs","lineNumber":358,"sourceCode":"        /// </summary>\n        /// <typeparam name=\"T\">解密后的强类型通知模型。</typeparam>\n        /// <param name=\"brandApiKey\">品牌 API 密钥。</param>\n        /// <param name=\"brandApiCredentials\">品牌 API 鉴权凭据，其中包含回调验签所需的微信支付公钥。</param>\n        /// <param name=\"nonce\">加密随机串；为空时读取通知资源中的值。</param>\n        /// <param name=\"associatedData\">附加数据；为空时读取通知资源中的值。</param>\n        /// <returns>验签并解密后的品牌通知。</returns>\n        public Task<T> DecryptBrandGetObjectAsync<T>(string brandApiKey,\n            TenPayBrandApiCredentials brandApiCredentials,\n            string nonce = null, string associatedData = null)\n            where T : ReturnJsonBase, new()\n        {\n            if (string.IsNullOrWhiteSpace(brandApiKey))\n            {\n                throw new ArgumentException(\"品牌 API 密钥不能为空。\",\n                    nameof(brandApiKey));\n            }\n\n            _ = brandApiCredentials ?? throw new ArgumentNullException(\n                nameof(brandApiCredentials));\n            var resource = NotifyRequest?.resource ?? throw new InvalidDataException(\n                \"通知正文中缺少加密资源 resource。\");\n\n            var wechatpayTimestamp =\n                _httpContext.Request.Headers?[\"Wechatpay-Timestamp\"].ToString();\n            var wechatpayNonce =\n                _httpContext.Request.Headers?[\"Wechatpay-Nonce\"].ToString();\n            var wechatpaySignature =\n                _httpContext.Request.Headers?[\"Wechatpay-Signature\"].ToString();\n            var wechatpaySerial =\n                _httpContext.Request.Headers?[\"Wechatpay-Serial\"].ToString();\n\n            if (!string.Equals(wechatpaySerial,\n                brandApiCredentials.WechatpayPublicKeyId,\n                StringComparison.Ordinal))\n            {\n                throw new InvalidOperationException(","sourceCodeStart":340,"sourceCodeEnd":376,"githubUrl":"https://github.com/JeffreySu/WeiXinMPSDK/blob/be573f6f94bdbf718dd5f6cdecb137fbc7ff651e/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/HttpHandlers/TenPayNotifyHandler.cs#L340-L376","documentation":"Same DecryptBrandGetObjectAsync flow as the brandApiKey guard: the method also requires TenPayBrandApiCredentials, which carry the WeChat Pay public key needed to verify the callback signature before decryption. A null credentials object means signature verification cannot be performed, so the method throws ArgumentNullException naming brandApiCredentials. Callers must provide the brand's credentials containing its public key.","triggerScenarios":"Invoking DecryptBrandGetObjectAsync with brandApiCredentials not constructed (null), typically when credential creation earlier failed silently.","commonSituations":"Config binding failures leaving credentials null, conditional wiring where brand credentials are only created for some environments, or refactoring that removed credential construction.","solutions":["Construct TenPayBrandApiCredentials (serial number + key material) before calling the decrypt method","Bind brand credentials from configuration at startup and fail fast if missing","Null-check the credentials object before invoking the method","Ensure the DI container registers TenPayBrandApiCredentials correctly"],"exampleFix":"// before\nawait handler.DecryptBrandGetObjectAsync<T>(apiKey, null);\n// after\nvar creds = new TenPayBrandApiCredentials(serialNumber, privateKey);\nawait handler.DecryptBrandGetObjectAsync<T>(apiKey, creds);","handlingStrategy":"type-guard","validationCode":"if (brandApiCredentials is null)\n    throw new InvalidOperationException(\"TenPayBrandApiCredentials 未初始化。\");","typeGuard":"bool HasBrandCredentials(TenPayBrandApiCredentials? c) => c is not null;","tryCatchPattern":"try { var obj = await handler.DecryptBrandGetObjectAsync<T>(apiKey, creds); }\ncatch (ArgumentNullException ex) when (ex.ParamName == nameof(brandApiCredentials)) { logger.LogError(ex, \"品牌凭据为空\"); }","preventionTips":["Register brand credentials in DI with validation on construction","Fail fast at startup if required credential material is missing"],"tags":["null-argument","tenpay","decryption"],"backgroundTag":"null-argument","analyzedSha":"be573f6f94bdbf718dd5f6cdecb137fbc7ff651e","analyzedAt":"2026-09-12T10:01:50.733Z","contentChangedAt":"2026-09-12T10:01:50.733Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}