{"record":{"id":"1a40277ab69ca6f5","repo":"Anankke/SSPanel-UIM","slug":"json-last-error-msg","errorCode":null,"errorMessage":"json_last_error_msg()","messagePattern":"json_last_error_msg\\(\\)","errorType":"exception","errorClass":"App\\Services\\Gateway\\Cryptomus\\RequestBuilderException","httpStatus":null,"severity":"error","filePath":"src/Services/Gateway/Cryptomus/RequestBuilder.php","lineNumber":63,"sourceCode":"                CURLOPT_URL => $url,\n                CURLOPT_HTTPHEADER => $headers,\n                CURLOPT_POST => 1,\n                CURLOPT_POSTFIELDS => $body,\n                CURLOPT_RETURNTRANSFER => 1,\n            ],\n        );\n\n        $response = curl_exec($curl);\n        $responseCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);\n\n        if ($response === false) {\n            throw new RequestBuilderException(curl_error($curl), $responseCode, $uri);\n        }\n\n        if ($response !== '') {\n            $json = json_decode($response, true);\n            if ($json === null) {\n                throw new RequestBuilderException(json_last_error_msg(), $responseCode, $uri);\n            }\n\n            if ($responseCode !== 200 || ($json['state'] !== null && $json['state'] !== 0)) {\n                if (isset($json['message']) && $json['message'] !== '') {\n                    throw new RequestBuilderException($json['message'], $responseCode, $uri);\n                }\n\n                if (isset($json['errors']) && $json['errors'] !== []) {\n                    throw new RequestBuilderException('Validation error', $responseCode, $uri, $json['errors']);\n                }\n            }\n\n            if (isset($json['result']) && $json['state'] !== null && $json['state'] === 0) {\n                return $json['result'];\n            }\n        }\n\n        return true;","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/Anankke/SSPanel-UIM/blob/d55a607191cfc51cdbc836fba85196ddef4df343/src/Services/Gateway/Cryptomus/RequestBuilder.php#L45-L81","documentation":"This exception is thrown when the Cryptomus endpoint returned a non-empty body that is not valid JSON — json_decode($response, true) produced null. The message is PHP's json_last_error_msg() (typically \"Syntax error\"), and the exception code is the real HTTP status, so a 502/503 HTML page from a proxy in front of the API surfaces here. It can also false-positive on a body that is literally the JSON literal \"null\" (json_decode('null') === null) even though that is technically valid JSON.","triggerScenarios":"api.cryptomus.com (or a CDN/proxy like Cloudflare in front of it) returns an HTML error page (502 Bad Gateway, 429 challenge/block page) instead of JSON; a rate-limit or WAF interceptor returns plain text (\"Too many requests\"); the response is valid JSON encoding the scalar null, e.g. the literal string \"null\", which json_decode maps to null and is misreported as a decode error; truncated body caused by a proxy cutting the connection mid-transfer.","commonSituations":"Shared-hosting IP being rate-limited or challenged by the gateway's CDN; transient upstream outage of Cryptomus returning HTML maintenance pages; hosting provider's transparent proxy mangling responses; scripts on old PHP versions where malformed UTF-8 bodies trigger \"Malformed UTF-8 characters\".","solutions":["Log the raw $response body (not just json_last_error_msg()) alongside the HTTP code — the HTML title of an error page immediately reveals whether the failure is Cloudflare/WAF, an origin 502, or truncation.","If the status is 429/5xx with HTML, back off and retry the request later; nothing in your payload is wrong.","If a genuine decode error repeats with status 200, capture the exact body bytes and report it to Cryptomus support / check for proxies stripping content.","Harden the check: distinguish 'null' from invalid JSON by comparing json_last_error() against JSON_ERROR_NONE instead of relying on the null return."],"exampleFix":"// before\n$json = json_decode($response, true);\nif ($json === null) {\n    throw new RequestBuilderException(json_last_error_msg(), $responseCode, $uri);\n}\n\n// after\n$json = json_decode($response, true);\nif (json_last_error() !== JSON_ERROR_NONE) {\n    throw new RequestBuilderException(\n        json_last_error_msg() . ': ' . substr($response, 0, 200),\n        $responseCode,\n        $uri\n    );\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    $result = $rb->sendRequest('v1/payment', $data);\n} catch (App\\Services\\Gateway\\Cryptomus\\RequestBuilderException $e) {\n    if (in_array($e->getCode(), [502, 503, 429], true) && str_contains($e->getMessage(), 'Syntax error')) {\n        // gateway/CDN returned a non-JSON page — transient, retry after backoff\n        return retryWithBackoff(fn () => $rb->sendRequest('v1/payment', $data), 3);\n    }\n    if (str_starts_with($e->getMessage(), 'Syntax error') || $e->getMessage() === 'Malformed UTF-8 characters') {\n        // persistent bad payload — log code + message and surface for investigation\n        $log->error('Cryptomus non-JSON response', ['status' => $e->getCode(), 'method' => $e->getMethod()]);\n    }\n    throw $e;\n}","preventionTips":["Log the HTTP status together with this exception — a 502/503 HTML page needs different handling than a 200 with a broken body.","Treat 'Syntax error' + 5xx as transient and retry; treat 'Syntax error' + 200 as a data/integration bug worth reporting.","When extending RequestBuilder, include a truncated raw body in the exception message so future occurrences are diagnosable from logs."],"tags":["php","json","cryptomus","api-response","proxy","gateway"],"backgroundTag":"invalid-json-response","analyzedSha":"d55a607191cfc51cdbc836fba85196ddef4df343","analyzedAt":"2026-08-21T04:59:05.849Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}