{"record":{"id":"22506a9d824dad1d","repo":"w7corp/easywechat","slug":"code2session-error-s","errorCode":null,"errorMessage":"code2Session error: %s","messagePattern":"code2Session error: (.+?)","errorType":"exception","errorClass":"HttpException","httpStatus":null,"severity":"error","filePath":"src/MiniApp/Utils.php","lineNumber":28,"sourceCode":"    {\n    }\n\n    /**\n     * @throws HttpException\n     */\n    public function codeToSession(string $code): array\n    {\n        $response = $this->app->getHttpClient()->request('GET', '/sns/jscode2session', [\n            'query' => [\n                'appid' => $this->app->getAccount()->getAppId(),\n                'secret' => $this->app->getAccount()->getSecret(),\n                'js_code' => $code,\n                'grant_type' => 'authorization_code',\n            ],\n        ])->toArray(false);\n\n        if (empty($response['openid'])) {\n            throw new HttpException('code2Session error: '.json_encode($response, JSON_UNESCAPED_UNICODE));\n        }\n\n        return $response;\n    }\n\n    public function decryptSession(string $sessionKey, string $iv, string $ciphertext): array\n    {\n        return Decryptor::decrypt($sessionKey, $iv, $ciphertext);\n    }\n\n    /**\n     * @throws HttpException\n     */\n    public function getPhoneNumber(string $code): array\n    {\n        $response = $this->app->createClient()->request('POST', '/wxa/business/getuserphonenumber', [\n            'json' => [\n                'code' => $code,","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/w7corp/easywechat/blob/f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8/src/MiniApp/Utils.php#L10-L46","documentation":"Utils::codeToSession() calls GET /sns/jscode2session and expects openid in the response; WeChat reports failures with errcode inside an HTTP 200 body (40029 invalid js_code, 40163 code already used, 45011 frequency limit, -1 system busy), so any response without openid becomes HttpException with the full JSON appended. This is the exchange step of mini-program login (wx.login code → openid + session_key).","triggerScenarios":"Exchanging a js_code twice (codes are single-use and short-lived); appid+secret pair mismatched (secret from another app, or reset in the console); calling jscode2session beyond the per-minute quota (45011); appid belonging to a different mini program than the code; empty secret because env vars differ per environment.","commonSituations":"WeChat dev tools (test appid) against a production secret; page refresh replaying the login with the same code; load tests tripping 45011; secret rotated in the console but not redeployed.","solutions":["Use each js_code exactly once, immediately after wx.login; request a fresh code on failure.","Confirm appid and secret come from the same mini program console entry and are current (re-copy the secret).","Decode the JSON inside the exception message and branch on errcode: 40029/40163 → ask the client for a new code; -1/45011 → retry with backoff.","Cache openid/session_key briefly after a successful exchange so retries do not re-exchange codes."],"exampleFix":"// before: replaying a consumed js_code\n$session = $app->getUtils()->codeToSession($sameOldCode);\n// after: branch on errcode and request a fresh code when invalid\ntry {\n    $session = $app->getUtils()->codeToSession($code);\n} catch (HttpException $e) {\n    if (str_contains($e->getMessage(), '40029')) {\n        return $this->askClientForNewCode();\n    }\n    throw $e;\n}","handlingStrategy":"retry","validationCode":"$code = trim($code);\nif ($code === '') { throw new InvalidArgumentException('js_code required'); }\nif (!Cache::add('js_code:'.$code, 1, 300)) { throw new RuntimeException('code already consumed'); }","typeGuard":null,"tryCatchPattern":"try { $session = $app->getUtils()->codeToSession($code); } catch (\\EasyWeChat\\Kernel\\Exceptions\\HttpException $e) { $body = json_decode(substr($e->getMessage(), (int) strpos($e->getMessage(), '{')), true); $err = (int) ($body['errcode'] ?? 0); if (in_array($err, [-1, 45011], true)) { usleep(500000); return $app->getUtils()->codeToSession($code); } // 40029/40163: ask the client for a fresh wx.login code throw new DomainException('wx login rejected: errcode '.$err); }","preventionTips":["Mark codes consumed (idempotency key) before exchanging","Keep appid/secret as one deployable unit","Retry only transient errcodes (-1, 45011) with backoff","Log the full response JSON on every failure"],"tags":["php","miniapp","login","jscode2session","wechat-api"],"backgroundTag":"oauth-code-exchange-failed","analyzedSha":"f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8","analyzedAt":"2026-08-21T05:29:19.565Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}