{"record":{"id":"97b7759184a7b217","repo":"signalapp/Signal-Server","slug":"login-purchases-are-not-enabled","errorCode":null,"errorMessage":"login purchases are not enabled","messagePattern":"login purchases are not enabled","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/controllers/LoginPurchaseController.java","lineNumber":108,"sourceCode":"      \"\"\")\n  @ApiResponse(responseCode = \"402\", description = \"The purchase did not complete successfully. The body may include ChargeFailure details.\",\n      content = @Content(schema = @Schema(\n          nullable = true,\n          implementation = SubscriptionExceptionMapper.ChargeFailureResponse.class)))\n  @ApiResponse(responseCode = \"403\", description = \"The request was made on an authenticated channel\")\n  @ApiResponse(responseCode = \"404\", description = \"The payment provider has no purchase with the provided purchaseIdentifier\")\n  @ApiResponse(responseCode = \"409\", description = \"The purchase was already redeemed for a receipt credential, but with a different receipt credential request\")\n  @ApiResponse(responseCode = \"429\", description = \"Too many attempts\", headers = @Header(\n      name = \"Retry-After\",\n      description = \"If present, a positive integer indicating the number of seconds before a subsequent attempt could succeed\"))\n  @ManagedAsync\n  public Response createLoginReceiptCredential(\n      @Auth final Optional<AuthenticatedDevice> authenticatedAccount,\n      @NotNull @Valid final CreateLoginReceiptCredentialRequest request)\n      throws IOException, SubscriptionPaymentRequiredException, SubscriptionInvalidArgumentsException, SubscriptionNotFoundException, RateLimitExceededException, SubscriptionReceiptAlreadyRedeemedException {\n\n    if (!dynamicConfigurationManager.getConfiguration().getLoginPurchaseConfiguration().enabled()) {\n      throw new BadRequestException(\"login purchases are not enabled\");\n    }\n\n    if (authenticatedAccount.isPresent()) {\n      throw new ForbiddenException(\"must not use authenticated connection for login purchase operations\");\n    }\n\n    final ReceiptCredentialRequest receiptCredentialRequest;\n    try {\n      receiptCredentialRequest = new ReceiptCredentialRequest(request.receiptCredentialRequest);\n    } catch (final InvalidInputException e) {\n      throw new BadRequestException(\"invalid receipt credential request\", e);\n    }\n\n    try {\n      final ReceiptCredentialResponse receiptCredentialResponse = loginPurchaseManager.generateReceipt(\n          request.paymentProvider, request.purchaseIdentifier, receiptCredentialRequest);\n      return Response.ok(\n              new CreateLoginReceiptCredentialResponse(receiptCredentialResponse.serialize()))","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/controllers/LoginPurchaseController.java#L90-L126","documentation":"LoginPurchaseController.createLoginReceiptCredential is gated by a server-side dynamic configuration flag, loginPurchaseConfiguration.enabled(). When the flag is off, the endpoint throws BadRequestException('login purchases are not enabled') before processing anything else. It is an intentional feature-gate, not a client bug.","triggerScenarios":"POST to the login purchase receipt-credential endpoint while the server's DynamicConfiguration loginPurchaseConfiguration.enabled() is false.","commonSituations":"Staging/self-hosted servers without the feature flag enabled in dynamic configuration; testing before an ops rollout; clients built against a server where the feature was temporarily disabled.","solutions":["Enable login purchases in the server's dynamic configuration (set loginPurchaseConfiguration.enabled to true and reload)","Wait for the operator to enable the feature flag if you don't control the server","Confirm you are pointing at the environment where the feature is enabled","Handle the 400 client-side by disabling login-purchase UI when the server rejects it"],"exampleFix":"# server dynamic configuration\n// before\n\"loginPurchase\": { \"enabled\": false }\n// after\n\"loginPurchase\": { \"enabled\": true }  # then reload dynamic configuration","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  client.createLoginReceiptCredential(request);\n} catch (WebApplicationException e) {\n  if (e.getResponse().getStatus() == 400 && String.valueOf(e.getResponse().getEntity()).contains(\"not enabled\")) {\n    // hide login-purchase UI; feature disabled server-side\n  } else throw e;\n}","preventionTips":["Check feature availability before showing login-purchase UI","Confirm the environment has the flag enabled before testing","Coordinate client releases with server feature rollouts"],"tags":["feature-flag","configuration","bad-request-400"],"backgroundTag":"feature-not-enabled","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}