{"record":{"id":"4e1aefe7959cf045","repo":"passbolt/passbolt_api","slug":"e-getmessage-from-subscriptionexception","errorCode":null,"errorMessage":"$e->getMessage() (from SubscriptionException)","messagePattern":"\\$e->getMessage\\(\\) \\(from SubscriptionException\\)","errorType":"http","errorClass":"PaymentRequiredException","httpStatus":402,"severity":"error","filePath":"plugins/PassboltCe/Edition/src/Controller/EditionSubscriptionsCreateController.php","lineNumber":55,"sourceCode":"     * @return void\n     */\n    public function create(): void\n    {\n        $this->User->assertIsAdmin();\n\n        $keyString = $this->getRequest()->getData('data');\n        if (!is_string($keyString) || trim($keyString) === '') {\n            throw new BadRequestException(__('Subscription key data is required.'));\n        }\n\n        $this->assertNotAlreadyPro();\n\n        try {\n            $keyDto = (new EditionUpgradeService())->upgrade($keyString, $this->User->getAccessControl());\n        } catch (SubscriptionSignatureException $e) {\n            throw new BadRequestException($e->getMessage());\n        } catch (SubscriptionException $e) {\n            throw new PaymentRequiredException($e->getMessage(), $e->getErrors());\n        }\n\n        $this->success(__('The subscription was created.'), $keyDto->toArray());\n    }\n\n    /**\n     * Rejects with HTTP 409 if the instance is already on PRO or already has a\n     * persisted subscription row.\n     *\n     * @return void\n     * @throws \\Cake\\Http\\Exception\\ConflictException\n     */\n    private function assertNotAlreadyPro(): void\n    {\n        if ((new EditionGetService())->get()->isPro()) {\n            throw new ConflictException(__('The instance is already on PRO.'));\n        }\n","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltCe/Edition/src/Controller/EditionSubscriptionsCreateController.php#L37-L73","documentation":"Other SubscriptionException failures from EditionUpgradeService::upgrade() (non-signature issues) are rethrown as PaymentRequiredException with the original message and errors. This typically means the key is structurally readable but rejected for subscription reasons such as expiry or licensing terms.","triggerScenarios":"upgrade() throws SubscriptionException other than a signature failure — e.g. expired subscription key, key data that cannot be parsed into a valid subscription record, or license terms violation.","commonSituations":"Importing an expired PRO key; renewing with a stale key; a key generated for a different passbolt edition; server clock skew making a valid key appear expired.","solutions":["Check the key's expiry date and obtain a renewed subscription key from passbolt","Read the error details returned in the PaymentRequired error body for the specific rejection reason","Synchronize the server clock (NTP) if expiry-related","Contact passbolt sales/support if the key should be valid"],"exampleFix":"// before\n// old expired key.txt used for import\n// after\n// download renewed subscription key and import it","handlingStrategy":"try-catch","validationCode":"// check key expiry metadata before import if available\nconst expired = keyMeta && new Date(keyMeta.expires) < new Date();","typeGuard":null,"tryCatchPattern":"try {\n  await api.post('/edition/subscriptions', {data: key});\n} catch (e) {\n  if (e.status === 402) { /* PaymentRequired: renew key; inspect e.errors for reason */ }\n}","preventionTips":["Track subscription expiry and renew proactively","Keep server clocks synchronized via NTP","Store error body details when a 402 is returned"],"tags":["subscription","payment","licensing"],"backgroundTag":"api-error-response","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T09:17:21.228Z"}