w7corp/easywechat · error · HttpException

getPhoneNumber error: %s

Error message

getPhoneNumber error: %s

What it means

Thrown by MiniApp\Utils::getPhoneNumber() when POST /wxa/business/getuserphonenumber returns a non-zero errcode. The raw WeChat response is JSON-encoded into the exception message, so the exact errcode/errmsg is always embedded. It means WeChat accepted the HTTP request but refused to release the phone number for the one-time code you supplied.

Source

Thrown at src/MiniApp/Utils.php:51

    public function decryptSession(string $sessionKey, string $iv, string $ciphertext): array
    {
        return Decryptor::decrypt($sessionKey, $iv, $ciphertext);
    }

    /**
     * @throws HttpException
     */
    public function getPhoneNumber(string $code): array
    {
        $response = $this->app->createClient()->request('POST', '/wxa/business/getuserphonenumber', [
            'json' => [
                'code' => $code,
            ],
        ])->toArray(false);

        if (isset($response['errcode']) && $response['errcode'] !== 0) {
            throw new HttpException('getPhoneNumber error: '.json_encode($response, JSON_UNESCAPED_UNICODE));
        }

        if (empty($response['phone_info'])) {
            throw new HttpException('getPhoneNumber error: '.json_encode($response, JSON_UNESCAPED_UNICODE));
        }

        return $response;
    }
}

View on GitHub (pinned to f0cf0a8b83)

Solutions

  1. json_decode the tail of the exception message and branch on errcode/errmsg (40029 = invalid code, 45011 = rate limited, permission-class codes point to account eligibility)
  2. Make the mini program request a brand-new code for every attempt and send it to the backend exactly once; never reuse a code across requests
  3. Confirm the mini program is enterprise-certified and the phone-number capability is enabled (and paid where required) in the MP console
  4. Verify the code was issued for the same appid the backend uses (no direct-vs-component mixups)

Example fix

// before: reusing a code captured earlier (single-use, short TTL)
$code = $session->get('phone_code');
$phone = $utils->getPhoneNumber($code);

// after: always consume the code from this request, exactly once
$code = (string) $request->input('code');
if ($code === '') {
    abort(422, 'phone code required');
}
$phone = $utils->getPhoneNumber($code);
Defensive patterns

Strategy: try-catch

Validate before calling

if ($code === '' || strlen($code) > 64) {
    throw new \InvalidArgumentException('A fresh open-type="getPhoneNumber" code is required.');
}

Try / catch

use EasyWeChat\Kernel\Exceptions\HttpException;

try {
    $phone = $utils->getPhoneNumber($code);
} catch (HttpException $e) {
    $payload = json_decode(strstr($e->getMessage(), '{') ?: '[]', true) ?: [];
    if (($payload['errcode'] ?? null) === 40029) {
        // code invalid/used: ask the mini program for a new one; do NOT retry the same code
    }
    report($e);
}

Prevention

When it happens

Trigger: Calling $utils->getPhoneNumber($code) with a code that is expired, already consumed (codes are single-use), truncated, or issued for a different appid; also when the mini program account is not eligible for the phone-number API (individual subject, not certified, or the paid phone-number service not enabled).

Common situations: Frontend caches or replays the code from the open-type="getPhoneNumber" button across retries/debugging; a developer copies a logged request and re-runs it manually; the mini program is a personal (个人) subject or lacks certification; the 2023+ paid phone-number billing is not activated in the MP console.

Related errors


AI-assisted analysis of w7corp/easywechat@f0cf0a8b83 (2026-08-21). Data as JSON: /api/errors/b8fdde1796b20197. Report an issue: GitHub.