{"record":{"id":"8590fadca35442e5","repo":"w7corp/easywechat","slug":"no-platform-certs-found-for-serial-serial","errorCode":null,"errorMessage":"No platform certs found for serial: {$serial}, \n                please download from wechat pay and set it in merchant config with key `certs`.","messagePattern":"No platform certs found for serial: (.+?), \n                please download from wechat pay and set it in merchant config with key `certs`\\.","errorType":"exception","errorClass":"InvalidConfigException","httpStatus":null,"severity":"error","filePath":"src/Pay/Validator.php","lineNumber":56,"sourceCode":"        }\n\n        [$timestamp] = $message->getHeader(self::HEADER_TIMESTAMP);\n        [$nonce] = $message->getHeader(self::HEADER_NONCE);\n        [$serial] = $message->getHeader(self::HEADER_SERIAL);\n        [$signature] = $message->getHeader(self::HEADER_SIGNATURE);\n\n        $body = (string) $message->getBody();\n\n        $message = \"{$timestamp}\\n{$nonce}\\n{$body}\\n\";\n\n        if (\\time() - \\intval($timestamp) > self::MAX_ALLOWED_CLOCK_OFFSET) {\n            throw new InvalidSignatureException('Clock Offset Exceeded');\n        }\n\n        $publicKey = $this->merchant->getPlatformCert($serial);\n\n        if (! $publicKey) {\n            throw new InvalidConfigException(\n                \"No platform certs found for serial: {$serial}, \n                please download from wechat pay and set it in merchant config with key `certs`.\"\n            );\n        }\n\n        if (\\openssl_verify(\n            $message,\n            base64_decode($signature),\n            strval($publicKey),\n            OPENSSL_ALGO_SHA256\n        ) !== 1) {\n            throw new InvalidSignatureException('Invalid Signature');\n        }\n    }\n}\n","sourceCodeStart":38,"sourceCodeEnd":72,"githubUrl":"https://github.com/w7corp/easywechat/blob/f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8/src/Pay/Validator.php#L38-L72","documentation":"After the header and timestamp checks, Validator::validate() must verify the signature with the WeChat Pay platform public key whose certificate serial matches the Wechatpay-Serial header (src/Pay/Validator.php:53-61). Merchant::getPlatformCert() looks the cert up in the platformCerts map (src/Pay/Merchant.php:62-65); when nothing is registered for that serial it throws InvalidConfigException. The map comes from the `platform_certs` config (src/Pay/Application.php:47), and list entries are keyed by their real certificate serial automatically (src/Pay/Merchant.php:91). Note: the message's hint `certs` is stale wording — the config key in this codebase is `platform_certs`.","triggerScenarios":"First webhook or first response validation before any platform cert was configured; WeChat Pay rotated its platform certificates so the new serial in Wechatpay-Serial has no matching entry; platform_certs passed as a map with hand-written wrong serial keys; multi-merchant apps sharing one cert set across different merchant accounts.","commonSituations":"New integrations that set only private_key, certificate and secret_key; production working for months then breaking after WeChat's periodic platform-cert rotation; environment-specific config files missing platform_certs; confusing the merchant certificate with the platform certificate.","solutions":["Fetch the current platform certs via the v3 certificates API (GET /v3/certificates using the SDK client, decrypt with secret_key via AES-256-GCM) and put the PEM public keys into `platform_certs`.","Or download the platform certificate from the WeChat Pay merchant console and add it: 'platform_certs' => ['-----BEGIN CERTIFICATE-----...'] — serials are derived from the certs themselves for list entries.","Keep superseded certs configured alongside new ones so a rotation never leaves a serial unmapped.","Add a runtime fallback: on this exception, re-download certs, persist them (config/cache), and retry validation exactly once."],"exampleFix":"// before: v3 credentials only\n$config = [\n    'mch_id' => '1900000000',\n    'secret_key' => '<api-v3-key>',\n    'private_key' => '...',\n    'certificate' => '...',\n];\n// first webhook -> \"No platform certs found for serial: 5157F09...\"\n\n// after: register platform certs (serial keys derived automatically)\n$config = [\n    'mch_id' => '1900000000',\n    'secret_key' => '<api-v3-key>',\n    'private_key' => '...',\n    'certificate' => '...',\n    'platform_certs' => [\n        '-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\\n',\n        '-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\\n', // keep old + new across rotations\n    ],\n];","handlingStrategy":"fallback","validationCode":"$serial = $request->getHeaderLine('Wechatpay-Serial');\n\nif ($serial !== '' && $app->getMerchant()->getPlatformCert($serial) === null) {\n    // Serial not mapped: platform certs missing or WeChat rotated them.\n    // GET /v3/certificates, decrypt with secret_key (AES-256-GCM),\n    // persist the public keys into `platform_certs`, rebuild the merchant,\n    // then continue.\n    refreshPlatformCertsFromApi($app);\n}\n\n$app->getValidator()->validate($request);","typeGuard":"use EasyWeChat\\Kernel\\Support\\PublicKey;\nuse EasyWeChat\\Pay\\Contracts\\Merchant as MerchantInterface;\n\nfunction hasPlatformCertForSerial(MerchantInterface $merchant, string $serial): bool\n{\n    return $merchant->getPlatformCert($serial) instanceof PublicKey;\n}","tryCatchPattern":"use EasyWeChat\\Kernel\\Exceptions\\InvalidConfigException;\n\ntry {\n    $app->getValidator()->validate($request);\n} catch (InvalidConfigException $e) {\n    if (str_contains($e->getMessage(), 'platform certs')) {\n        refreshPlatformCertsFromApi($app); // download + persist, keeping old certs mapped\n\n        return $app->getValidator()->validate($request); // retry exactly once\n    }\n\n    throw $e;\n}","preventionTips":["Provision platform_certs during deployment, not lazily on the first webhook","After fetching /v3/certificates, keep superseded certs mapped until rotation fully completes","Automate cert refresh on a schedule and on unknown-serial errors","Give each merchant account its own platform_certs set in multi-merchant apps"],"tags":["wechat-pay","certificates","configuration","webhook","php"],"backgroundTag":"missing-certificate","analyzedSha":"f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8","analyzedAt":"2026-08-21T05:29:19.565Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}