{"record":{"id":"140e8b948e3a68cf","repo":"w7corp/easywechat","slug":"missing-v2-secret-key","errorCode":null,"errorMessage":"Missing v2 secret key.","messagePattern":"Missing v2 secret key\\.","errorType":"exception","errorClass":"InvalidConfigException","httpStatus":null,"severity":"error","filePath":"src/Pay/Utils.php","lineNumber":182,"sourceCode":"            throw new InvalidConfigException('Missing platform certificate.');\n        }\n\n        if (! openssl_public_encrypt($plaintext, $encrypted, $platformCert, OPENSSL_PKCS1_OAEP_PADDING)) {\n            throw new EncryptionFailureException('Encrypt failed.');\n        }\n\n        return base64_encode($encrypted);\n    }\n\n    /**\n     * @throws InvalidConfigException\n     */\n    public function createV2Signature(array $params): string\n    {\n        $secretKey = $this->merchant->getV2SecretKey();\n\n        if (empty($secretKey)) {\n            throw new InvalidConfigException('Missing v2 secret key.');\n        }\n\n        ksort($params);\n\n        $params['key'] = $secretKey;\n\n        $message = urldecode(http_build_query($params));\n\n        if ($params['signType'] === 'HMAC-SHA256') {\n            $signature = hash_hmac('sha256', $message, $secretKey);\n        } else {\n            $signature = md5($message);\n        }\n\n        return strtoupper($signature);\n    }\n}\n","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/w7corp/easywechat/blob/f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8/src/Pay/Utils.php#L164-L200","documentation":"Thrown by Utils::createV2Signature() (src/Pay/Utils.php:182) when the merchant has no WeChat Pay APIv2 key. This method implements legacy v2 signing: ksort() the params, append the key as `key`, then produce an HMAC-SHA256 or MD5 signature over the urldecoded query string. The v2 key (config `v2_secret_key`, see src/Pay/Merchant.php:31 and src/Pay/Application.php:46) is a separate 32-character key from the v3 `secret_key`, so a fully working v3 setup can still hit this. It is raised as InvalidConfigException before any crypto runs, via empty() on getV2SecretKey().","triggerScenarios":"Calling Utils::createV2Signature() or any v2-style signing path (LegacySignature, src/Pay/LegacySignature.php:48-57), or handling v2 XML callbacks whose req_info is decrypted with md5(v2 key) (src/Pay/Server.php:223-226), while the pay config has no `v2_secret_key` set — Application.php:46 casts the missing value to an empty string, so getV2SecretKey() returns '' and empty() trips.","commonSituations":"Config only sets v3 credentials (secret_key, private_key, certificate) but a refund, red-pack or other legacy endpoint still uses v2; key pasted under the wrong config name; .env value not loaded or config cache stale in one environment; secrets manager returning an empty string.","solutions":["Add 'v2_secret_key' => '<32-char key>' to the pay config — get it in the WeChat Pay merchant console under Account Center > API Security > APIv2 key (账户中心 > API安全 > APIv2密钥).","If the integration only needs v3, switch the offending call to the v3 API equivalent and drop the v2 signing path.","Verify the value actually loads where the call runs: dump the pay config, check .env, config cache and environment-specific overrides.","Confirm the key is exactly 32 characters with no surrounding whitespace or quotes."],"exampleFix":"// before\n$config = [\n    'mch_id' => '1900000000',\n    'secret_key' => '<api-v3-key>',\n    'private_key' => '...',\n    'certificate' => '...',\n    // v2_secret_key missing -> createV2Signature() throws\n];\n$sign = $app->getUtils()->createV2Signature($params);\n\n// after\n$config = [\n    'mch_id' => '1900000000',\n    'secret_key' => '<api-v3-key>',\n    'private_key' => '...',\n    'certificate' => '...',\n    'v2_secret_key' => '<32-char-api-v2-key>',\n];\n$sign = $app->getUtils()->createV2Signature($params);","handlingStrategy":"validation","validationCode":"use EasyWeChat\\Kernel\\Exceptions\\InvalidConfigException;\n\nif (empty($app->getMerchant()->getV2SecretKey())) {\n    throw new InvalidConfigException(\n        'WeChat Pay v2_secret_key is not configured; set it before using v2 signing.'\n    );\n}\n\n$sign = $app->getUtils()->createV2Signature($params);","typeGuard":null,"tryCatchPattern":"use EasyWeChat\\Kernel\\Exceptions\\InvalidConfigException;\n\ntry {\n    $sign = $utils->createV2Signature($params);\n} catch (InvalidConfigException $e) {\n    // Config problem, not runtime: surface it — never send unsigned v2 requests\n    Log::error('wechat-pay v2 signing unavailable: {msg}', ['msg' => $e->getMessage()]);\n    throw $e;\n}","preventionTips":["Keep v2_secret_key next to secret_key in the pay config so v3-only setups don't silently omit it","Run a boot-time check that required config keys are non-empty (secret_key, private_key, certificate, and v2_secret_key whenever any v2 path is used)","Load keys from env or a secret manager; never fall back to empty strings","Add a canary v2 sign call to deployment smoke tests when v2 APIs are in use"],"tags":["wechat-pay","php","configuration","api-key","signature"],"backgroundTag":"missing-api-key","analyzedSha":"f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8","analyzedAt":"2026-08-21T05:29:19.565Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}