{"record":{"id":"99df22283393f693","repo":"w7corp/easywechat","slug":"invalid-request-resource","errorCode":null,"errorMessage":"Invalid request resource.","messagePattern":"Invalid request resource\\.","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"src/Pay/Server.php","lineNumber":274,"sourceCode":"    protected function decodeJsonMessage(string $contents): array\n    {\n        $attributes = json_decode($contents, true);\n\n        if (! (is_array($attributes) && is_array($attributes['resource']))) {\n            throw new RuntimeException('Invalid request body.');\n        }\n\n        $resource = $attributes['resource'];\n        $ciphertext = $resource['ciphertext'] ?? null;\n        $nonce = $resource['nonce'] ?? null;\n        $associatedData = $resource['associated_data'] ?? null;\n\n        if (! is_string($ciphertext) || $ciphertext === '') {\n            throw new RuntimeException('Invalid request.');\n        }\n\n        if (! is_string($nonce) || ! is_string($associatedData)) {\n            throw new RuntimeException('Invalid request resource.');\n        }\n\n        $attributes = json_decode(\n            AesGcm::decrypt(\n                $ciphertext,\n                $this->merchant->getSecretKey(),\n                $nonce,\n                $associatedData,\n            ),\n            true\n        );\n\n        if (! is_array($attributes)) {\n            throw new RuntimeException('Failed to decrypt request message.');\n        }\n\n        return $attributes;\n    }","sourceCodeStart":256,"sourceCodeEnd":292,"githubUrl":"https://github.com/w7corp/easywechat/blob/f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8/src/Pay/Server.php#L256-L292","documentation":"Thrown by Pay/Server::decodeJsonMessage() when resource.nonce or resource.associated_data is not a string. These two fields are mandatory inputs for AES-256-GCM decryption of resource.ciphertext; if either is missing or has the wrong type, decryption cannot proceed.","triggerScenarios":"A V3 notification whose resource object misses nonce or associated_data (or contains null/int), typically from hand-crafted test payloads, partial body truncation, or an intermediary re-encoding JSON and coercing values (e.g. empty nonce removed by an array_filter on the sender side).","commonSituations":"Dev fixtures built from documentation snippets that omit associated_data for older notifications; body clipping at size limits losing trailing fields; JSON re-serialization that converts '' to null.","solutions":["Log the raw body at the route and diff with a real WeChat notification (all three resource fields present, strings, possibly empty associated_data)","Replay a genuine notification (WeChat Pay console -> re-push) instead of synthetic payloads","If you build notifications yourself (sandbox), always include nonce and associated_data as strings"],"exampleFix":"// before - sandbox push missing nonce\n$resource = ['ciphertext' => $ct /* , 'nonce' forgotten */];\n// after\n$resource = ['ciphertext' => $ct, 'nonce' => $nonce, 'associated_data' => 'transaction'];","handlingStrategy":"type-guard","validationCode":"$ok = is_string($resource['nonce'] ?? null)\n    && is_string($resource['associated_data'] ?? null);\nif (! $ok) { return response('fail', 400); }","typeGuard":"function hasGcmInputs(array $resource): bool\n{\n    return is_string($resource['nonce'] ?? null) && is_string($resource['associated_data'] ?? null);\n}","tryCatchPattern":null,"preventionTips":["Keep nonce and associated_data as strings when building sandbox notifications (empty string is fine, absence is not)","Never array_filter notification resource objects","Validate shape at the boundary and 400 early"],"tags":["wechat-pay","webhook","json-parsing","payload-malformed"],"backgroundTag":"webhook-payload-invalid","analyzedSha":"f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8","analyzedAt":"2026-08-21T05:29:19.565Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}