w7corp/easywechat · error · RuntimeException

Invalid request resource.

Error message

Invalid request resource.

What it means

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.

Source

Thrown at src/Pay/Server.php:274

    protected function decodeJsonMessage(string $contents): array
    {
        $attributes = json_decode($contents, true);

        if (! (is_array($attributes) && is_array($attributes['resource']))) {
            throw new RuntimeException('Invalid request body.');
        }

        $resource = $attributes['resource'];
        $ciphertext = $resource['ciphertext'] ?? null;
        $nonce = $resource['nonce'] ?? null;
        $associatedData = $resource['associated_data'] ?? null;

        if (! is_string($ciphertext) || $ciphertext === '') {
            throw new RuntimeException('Invalid request.');
        }

        if (! is_string($nonce) || ! is_string($associatedData)) {
            throw new RuntimeException('Invalid request resource.');
        }

        $attributes = json_decode(
            AesGcm::decrypt(
                $ciphertext,
                $this->merchant->getSecretKey(),
                $nonce,
                $associatedData,
            ),
            true
        );

        if (! is_array($attributes)) {
            throw new RuntimeException('Failed to decrypt request message.');
        }

        return $attributes;
    }

View on GitHub (pinned to f0cf0a8b83)

Solutions

  1. Log the raw body at the route and diff with a real WeChat notification (all three resource fields present, strings, possibly empty associated_data)
  2. Replay a genuine notification (WeChat Pay console -> re-push) instead of synthetic payloads
  3. If you build notifications yourself (sandbox), always include nonce and associated_data as strings

Example fix

// before - sandbox push missing nonce
$resource = ['ciphertext' => $ct /* , 'nonce' forgotten */];
// after
$resource = ['ciphertext' => $ct, 'nonce' => $nonce, 'associated_data' => 'transaction'];
Defensive patterns

Strategy: type-guard

Validate before calling

$ok = is_string($resource['nonce'] ?? null)
    && is_string($resource['associated_data'] ?? null);
if (! $ok) { return response('fail', 400); }

Type guard

function hasGcmInputs(array $resource): bool
{
    return is_string($resource['nonce'] ?? null) && is_string($resource['associated_data'] ?? null);
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


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