{"record":{"id":"4fac978cf2a7746f","repo":"w7corp/easywechat","slug":"request-failed","errorCode":null,"errorMessage":"Request Failed","messagePattern":"Request Failed","errorType":"exception","errorClass":"BadResponseException","httpStatus":null,"severity":"error","filePath":"src/Pay/ResponseValidator.php","lineNumber":28,"sourceCode":"use Psr\\Http\\Message\\ResponseInterface as PsrResponse;\n\nclass ResponseValidator implements Contracts\\ResponseValidator\n{\n    public function __construct(protected MerchantInterface $merchant)\n    {\n    }\n\n    /**\n     * @throws BadResponseException\n     */\n    public function validate(PsrResponse|HttpClientResponse $response): void\n    {\n        if ($response instanceof HttpClientResponse) {\n            $response = $response->toPsrResponse();\n        }\n\n        if ($response->getStatusCode() !== 200) {\n            throw new BadResponseException('Request Failed');\n        }\n\n        (new Validator($this->merchant))->validate($response);\n    }\n}\n","sourceCodeStart":10,"sourceCodeEnd":34,"githubUrl":"https://github.com/w7corp/easywechat/blob/f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8/src/Pay/ResponseValidator.php#L10-L34","documentation":"Thrown by Pay/ResponseValidator::validate() when the WeChat Pay API response carries a non-200 status. This validator is used for specific flows (e.g. after upload/certificate operations) and checks transport-level success before running signature validation, so any 4xx/5xx (401 bad signature, 403, 429 rate limit, 500) surfaces as this generic BadResponseException without the body.","triggerScenarios":"Calling the endpoints wrapped by this validator (e.g. marketing/filer-image-upload flows that use ResponseValidator, or any call path where validate() runs) and WeChat Pay answering 401 (Authorization signature wrong), 400 (bad request), 429 (too many requests), or 503.","commonSituations":"Wrong merchant serial in the Authorization header (401); expired/incorrect merchant private key; hammering the API past rate limits (429) during batch jobs; WeChat Pay incidents returning 5xx; clock skew breaking the signature timestamp.","solutions":["Reproduce the raw request and capture the response body — it contains code/message telling the real cause (401 = signature/serial, 429 = slow down)","Verify the merchant certificate serial + private key pair configured for the client match the one uploaded in the merchant console","Add backoff-and-retry around 429/5xx; do not retry 4xx","Sync server time (NTP) since signature includes timestamp and skew yields 401"],"exampleFix":"// before\ntry {\n    $api->postJson('/v3/...', $payload);\n} catch (\\Symfony\\Contracts\\HttpClient\\Exception\\TransportExceptionInterface $e) { /* ... */ }\n// after - inspect HTTP status before validating, retry transient failures\n$response = $client->request('POST', $url, $options);\nif (in_array($response->getStatusCode(), [429, 500, 502, 503], true)) { retryWithBackoff($fn); }","handlingStrategy":"retry","validationCode":"$status = $response->getStatusCode();\nif ($status !== 200) {\n    $body = $response->getContent(false); // capture code/message\n    // decide: 4xx -> fail, 429/5xx -> retry\n}","typeGuard":null,"tryCatchPattern":"use Symfony\\Contracts\\HttpClient\\Exception\\HttpExceptionInterface;\ntry {\n    $app->client->postJson($uri, $payload);\n} catch (\\EasyWeChat\\Kernel\\Exceptions\\BadResponseException $e) {\n    // inspect upstream body for code/message; only retry on 429/5xx with backoff\n}","preventionTips":["Capture response bodies for non-200 to surface WeChat's code/message","Rate-limit batch jobs to stay under quotas","Keep merchant serial/private key consistent and NTP-synced to avoid 401"],"tags":["wechat-pay","http-status","response-validation","rate-limit"],"backgroundTag":"api-http-status-error","analyzedSha":"f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8","analyzedAt":"2026-08-21T05:29:19.565Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}