JeffreySu/WeiXinMPSDK · error · ArgumentNullException

brandApiCredentials

Error message

brandApiCredentials

What it means

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.

Solutions

  1. Construct TenPayBrandApiCredentials (serial number + key material) before calling the decrypt method
  2. Bind brand credentials from configuration at startup and fail fast if missing
  3. Null-check the credentials object before invoking the method
  4. Ensure the DI container registers TenPayBrandApiCredentials correctly

Example fix

// before
await handler.DecryptBrandGetObjectAsync<T>(apiKey, null);
// after
var creds = new TenPayBrandApiCredentials(serialNumber, privateKey);
await handler.DecryptBrandGetObjectAsync<T>(apiKey, creds);
Defensive patterns

Strategy: type-guard

Validate before calling

if (brandApiCredentials is null)
    throw new InvalidOperationException("TenPayBrandApiCredentials 未初始化。");

Type guard

bool HasBrandCredentials(TenPayBrandApiCredentials? c) => c is not null;

Try / catch

try { var obj = await handler.DecryptBrandGetObjectAsync<T>(apiKey, creds); }
catch (ArgumentNullException ex) when (ex.ParamName == nameof(brandApiCredentials)) { logger.LogError(ex, "品牌凭据为空"); }

Prevention

When it happens

Trigger: Invoking DecryptBrandGetObjectAsync with brandApiCredentials not constructed (null), typically when credential creation earlier failed silently.

Common situations: Config binding failures leaving credentials null, conditional wiring where brand credentials are only created for some environments, or refactoring that removed credential construction.

Related errors


AI-assisted analysis of JeffreySu/WeiXinMPSDK@be573f6f94 (2026-09-12). Data as JSON: /api/errors/309dbd4cc485a308. Report an issue: GitHub.

Appendix: source

Thrown at src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/HttpHandlers/TenPayNotifyHandler.cs:358

        /// </summary>
        /// <typeparam name="T">解密后的强类型通知模型。</typeparam>
        /// <param name="brandApiKey">品牌 API 密钥。</param>
        /// <param name="brandApiCredentials">品牌 API 鉴权凭据,其中包含回调验签所需的微信支付公钥。</param>
        /// <param name="nonce">加密随机串;为空时读取通知资源中的值。</param>
        /// <param name="associatedData">附加数据;为空时读取通知资源中的值。</param>
        /// <returns>验签并解密后的品牌通知。</returns>
        public Task<T> DecryptBrandGetObjectAsync<T>(string brandApiKey,
            TenPayBrandApiCredentials brandApiCredentials,
            string nonce = null, string associatedData = null)
            where T : ReturnJsonBase, new()
        {
            if (string.IsNullOrWhiteSpace(brandApiKey))
            {
                throw new ArgumentException("品牌 API 密钥不能为空。",
                    nameof(brandApiKey));
            }

            _ = brandApiCredentials ?? throw new ArgumentNullException(
                nameof(brandApiCredentials));
            var resource = NotifyRequest?.resource ?? throw new InvalidDataException(
                "通知正文中缺少加密资源 resource。");

            var wechatpayTimestamp =
                _httpContext.Request.Headers?["Wechatpay-Timestamp"].ToString();
            var wechatpayNonce =
                _httpContext.Request.Headers?["Wechatpay-Nonce"].ToString();
            var wechatpaySignature =
                _httpContext.Request.Headers?["Wechatpay-Signature"].ToString();
            var wechatpaySerial =
                _httpContext.Request.Headers?["Wechatpay-Serial"].ToString();

            if (!string.Equals(wechatpaySerial,
                brandApiCredentials.WechatpayPublicKeyId,
                StringComparison.Ordinal))
            {
                throw new InvalidOperationException(

View on GitHub (pinned to be573f6f94)