{"record":{"id":"64b997717df08929","repo":"JeffreySu/WeiXinMPSDK","slug":"api-paytoolsignaturehelper","errorCode":null,"errorMessage":"收银台 API 调用密钥不能为空。","messagePattern":"收银台 API 调用密钥不能为空。","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/Senparc.Weixin.Work/Senparc.Weixin.Work/AdvancedAPIs/PayTool/PayToolSignatureHelper.cs","lineNumber":89,"sourceCode":"        }\n\n        /// <summary>\n        /// 按企业微信规则递归展开非空叶子参数、按完整 key=value 字符串升序排列，\n        /// 再使用收银台 API 调用密钥计算 HMAC-SHA256 并进行 Base64 编码。\n        /// </summary>\n        /// <param name=\"request\">请求对象；任意层级名为 sig 的字段不参与签名。</param>\n        /// <param name=\"payToolApiSecret\">收银台 API 调用密钥。</param>\n        /// <returns>Base64 编码的数字签名。</returns>\n        public static string CreateSignature(object request, string payToolApiSecret)\n        {\n            if (request == null)\n            {\n                throw new ArgumentNullException(nameof(request));\n            }\n\n            if (string.IsNullOrEmpty(payToolApiSecret))\n            {\n                throw new ArgumentException(\"收银台 API 调用密钥不能为空。\",\n                    nameof(payToolApiSecret));\n            }\n\n            var pairs = new List<string>();\n            CollectPairs(null, JToken.FromObject(request), pairs);\n            pairs.Sort(StringComparer.Ordinal);\n            var signingText = string.Join(\"&\", pairs);\n\n            using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(payToolApiSecret)))\n            {\n                return Convert.ToBase64String(\n                    hmac.ComputeHash(Encoding.UTF8.GetBytes(signingText)));\n            }\n        }\n\n        private static void CollectPairs(string key, JToken token, ICollection<string> pairs)\n        {\n            if (token == null || token.Type == JTokenType.Null ||","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/JeffreySu/WeiXinMPSDK/blob/be573f6f94bdbf718dd5f6cdecb137fbc7ff651e/src/Senparc.Weixin.Work/Senparc.Weixin.Work/AdvancedAPIs/PayTool/PayToolSignatureHelper.cs#L71-L107","documentation":"CreateSignature throws this ArgumentException when payToolApiSecret is null or empty. The signature is computed as HMAC-style signing over the sorted request key/value pairs using this secret, so an absent secret makes signing impossible. PrepareRequest invokes CreateSignature when it must generate sig itself, which propagates this error.","triggerScenarios":"Calling CreateSignature (directly or via PrepareRequest without a sig in the request) with payToolApiSecret set to null or \"\".","commonSituations":"Missing cashier API secret in configuration files, environment variable not set, or secret left as empty string after copying config templates, or upgraded library versions that newly require the secret parameter.","solutions":["Provide the cashier API secret from your WeChat Work merchant configuration.","Add a startup config check that fails fast when the secret is missing/empty.","Confirm you are using the correct secret for the PayTool cashier API (not other WeChat Work tokens)."],"exampleFix":"// before\nvar sig = PayToolSignatureHelper.CreateSignature(request, config.PayToolSecret); // PayToolSecret == \"\"\n// after\nif (string.IsNullOrEmpty(config.PayToolSecret))\n    throw new InvalidOperationException(\"请在配置中填写收银台 API 调用密钥\");\nvar sig = PayToolSignatureHelper.CreateSignature(request, config.PayToolSecret);","handlingStrategy":"validation","validationCode":"if (string.IsNullOrEmpty(payToolApiSecret)) throw new InvalidOperationException(\"收银台 API 密钥未配置\");","typeGuard":null,"tryCatchPattern":"try { var sig = CreateSignature(request, secret); } catch (ArgumentException ex) when (ex.ParamName == nameof(payToolApiSecret)) { logger.LogError(ex, \"签名密钥为空\"); throw new ConfigurationException(\"请配置收银台 API 调用密钥\"); }","preventionTips":["Validate all required secrets at application startup, not at first API call.","Keep secrets in a single config section with load-time assertions.","Document which secret belongs to which API surface (cashier vs. other WeChat Work credentials)."],"tags":["argument","validation","signature","missing-secret"],"backgroundTag":"missing-credentials","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"}