{"record":{"id":"0fa50e62a6522ecf","repo":"JeffreySu/WeiXinMPSDK","slug":"api-id-tenpaynotifyhandler","errorCode":null,"errorMessage":"品牌 API 通知的微信支付公钥 ID 与配置不匹配。","messagePattern":"品牌 API 通知的微信支付公钥 ID 与配置不匹配。","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/HttpHandlers/TenPayNotifyHandler.cs","lineNumber":376,"sourceCode":"            _ = 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(\n                    \"品牌 API 通知的微信支付公钥 ID 与配置不匹配。\");\n            }\n\n            var verifySignSuccess = TenPaySignHelper.VerifyTenpaySign(\n                CertType.RSA, wechatpayTimestamp, wechatpayNonce,\n                wechatpaySignature, Body,\n                brandApiCredentials.WechatpayPublicKey, true);\n            var decryptedString = SecurityHelper.AesGcmDecryptCiphertext(\n                brandApiKey, nonce ?? resource.nonce,\n                associatedData ?? resource.associated_data,\n                resource.ciphertext);\n            var result = decryptedString.GetObject<T>();\n            result.VerifySignSuccess = verifySignSuccess;\n            result.ResultCode = new TenPayApiResultCode(\n                $\"{_httpContext.Response.StatusCode} / {_httpContext.Request.Method}\",\n                \"\", \"\", \"\", result.VerifySignSuccess == true);\n\n            return Task.FromResult(result);","sourceCodeStart":358,"sourceCodeEnd":394,"githubUrl":"https://github.com/JeffreySu/WeiXinMPSDK/blob/be573f6f94bdbf718dd5f6cdecb137fbc7ff651e/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/HttpHandlers/TenPayNotifyHandler.cs#L358-L394","documentation":"TenPayV3's brand-API notify handler reads the Wechatpay-Serial header from the incoming callback and compares it (Ordinal, exact) with the WechatpayPublicKeyId configured in brandApiCredentials. When the public key ID presented by WeChat Pay does not equal the configured one, decryption of the callback resource is refused and this InvalidOperationException is thrown to prevent decrypting/verifying with the wrong key.","triggerScenarios":"An inbound brand API notification (DecryptBrandGetObjectAsync) whose Wechatpay-Serial header differs from brandApiCredentials.WechatpayPublicKeyId — e.g. WeChat rotated to a new public key, the merchant platform switched between platform certificate mode and public key mode, or the configured key ID is stale/typo'd.","commonSituations":"Merchant enabled the new '微信支付公钥' mode but the app still configures an old public key ID; WeChat Pay published a new public key version; config copied from a test merchant account into production; key ID string copied with whitespace or wrong case (Ordinal comparison).","solutions":["Log in to WeChat Pay merchant platform and copy the current 微信支付公钥 ID (Pub Key ID) exactly into brandApiCredentials.WechatpayPublicKeyId.","Check the Wechatpay-Serial header of the failing notification against your configured value to confirm which key version WeChat is using.","If you support multiple key versions, update the handler/config to look up the verifier by the serial from the header instead of a single fixed ID.","Ensure the configured value has no whitespace/BOM and matches case exactly (the comparison is StringComparison.Ordinal)."],"exampleFix":"// before\nbrandApiCredentials.WechatpayPublicKeyId = \"OLD-PUB-KEY-ID\";\n// after\nbrandApiCredentials.WechatpayPublicKeyId = \"PUB_KEY_ID_0114xxxxxxxx\"; // current value from WeChat Pay merchant platform","handlingStrategy":"validation","validationCode":"var serial = request.Headers[\"Wechatpay-Serial\"].ToString();\nif (!string.Equals(serial, brandApiCredentials.WechatpayPublicKeyId, StringComparison.Ordinal))\n    return Results.StatusCode(500); // reject before calling DecryptBrandGetObjectAsync","typeGuard":null,"tryCatchPattern":"try { var data = await handler.DecryptBrandGetObjectAsync(...); }\ncatch (InvalidOperationException ex) { logger.LogWarning(ex, \"Wechatpay serial mismatch\"); return Results.StatusCode(500); }","preventionTips":["Keep the WechatpayPublicKeyId in config sourced directly from the merchant platform, without manual retyping.","Support multiple key versions by looking up keys by the incoming serial rather than a single fixed ID.","Monitor WeChat Pay announcements for public key rotation."],"tags":["wechat-pay","signature-verification","config-mismatch","callback"],"backgroundTag":"invalid-config-value","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"}