{"record":{"id":"a1f1c92fc52a8fbd","repo":"signalapp/Signal-Server","slug":"unknown-payment-provider","errorCode":null,"errorMessage":"unknown payment provider: ","messagePattern":"unknown payment provider: ","errorType":"exception","errorClass":"SubscriptionInvalidArgumentsException","httpStatus":null,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/LoginPurchaseManager.java","lineNumber":65,"sourceCode":"  ///\n  /// Repeated calls must use the same `receiptCredentialRequest`; a second request for a purchase that was already\n  /// redeemed fails with [SubscriptionReceiptAlreadyRedeemedException].\n  ///\n  /// @param paymentProvider          The provider that processed the purchase\n  /// @param purchaseId               The identifier for the purchase in the `paymentProvider`\n  /// @param receiptCredentialRequest The request for the receipt to generate. All retries for the same purchaseId must\n  /// use the same request\n  /// @return The receipt credential\n  ///\n  public ReceiptCredentialResponse generateReceipt(\n      final PaymentProvider paymentProvider,\n      final String purchaseId,\n      final ReceiptCredentialRequest receiptCredentialRequest)\n      throws RateLimitExceededException, SubscriptionInvalidArgumentsException, IOException, SubscriptionNotFoundException, SubscriptionReceiptRequestedForOpenPaymentException, SubscriptionPaymentRequiredException, SubscriptionReceiptAlreadyRedeemedException, VerificationFailedException {\n\n    final OneTimePaymentProcessor oneTimePaymentProcessor = oneTimePaymentProcessors.get(paymentProvider);\n    if (oneTimePaymentProcessor == null) {\n      throw new SubscriptionInvalidArgumentsException(\"unknown payment provider: \" + paymentProvider);\n    }\n\n    final PaymentDetails paymentDetails = oneTimePaymentProcessor\n        .claimOneTimePurchase(purchaseId)\n        .orElseThrow(SubscriptionNotFoundException::new);\n    if (paymentDetails.status() == PaymentStatus.PROCESSING) {\n      throw new SubscriptionReceiptRequestedForOpenPaymentException();\n    } else if (paymentDetails.status() != PaymentStatus.SUCCEEDED) {\n      throw Optional.ofNullable(paymentDetails.chargeFailure())\n          .<SubscriptionPaymentRequiredException>map(\n              cf -> new SubscriptionChargeFailurePaymentRequiredException(paymentProvider, cf))\n          .orElseGet(SubscriptionPaymentRequiredException::new);\n    } else if (paymentDetails.level() != ReceiptLevel.LOGIN) {\n      throw new SubscriptionInvalidArgumentsException(\"purchase was for an unexpected product\");\n    }\n\n    // Calculating the expiration from the creation date works for IAP purchases. However, for other processors, the\n    // creation date of the payment intent might be days before the payment actually completed. If we support non-IAP","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/LoginPurchaseManager.java#L47-L83","documentation":"LoginPurchaseManager.generateReceipt resolves a OneTimePaymentProcessor by PaymentProvider; if the map has no processor for the given provider it throws SubscriptionInvalidArgumentsException with \"unknown payment provider: \" + paymentProvider. Only providers with registered one-time payment processors (e.g. specific IAP providers configured in the server) are accepted.","triggerScenarios":"Client submits a receipt/generateReceipt request whose paymentProvider enum value is valid per the API but has no one-time payment processor registered server-side — e.g. STRIPE or BRAINTREE passed for a login purchase flow that only supports Apple/Google IAP.","commonSituations":"Client built against a newer payment-provider list than the server supports; server deployed without the processor bean configured; client sends the wrong provider for the login (vs donation) flow.","solutions":["Check the paymentProvider value the client sends; for one-time login purchases it must match a supported IAP provider (APPLE_APP_STORE / GOOGLE_PLAY_BILLING).","Verify server configuration registers the oneTimePaymentProcessors map entry for that provider (feature/deployment config).","Align client and server versions so the payment-provider enum matches.","If the provider should be supported, register its OneTimePaymentProcessor implementation at server startup."],"exampleFix":"// before: wrong provider for one-time purchase flow\napi.generateReceipt(PaymentProvider.STRIPE, purchaseId, request);\n// after\napi.generateReceipt(PaymentProvider.APPLE_APP_STORE, purchaseId, request);","handlingStrategy":"validation","validationCode":"// keep the client-side allowlist in sync with server-registered processors\nconst supported = ['APPLE_APP_STORE', 'GOOGLE_PLAY_BILLING'];\nif (!supported.includes(paymentProvider.name())) { throw new Error('unsupported provider for one-time purchase'); }","typeGuard":"function isSupportedOneTimeProvider(p) { return p === 'APPLE_APP_STORE' || p === 'GOOGLE_PLAY_BILLING'; }","tryCatchPattern":"try {\n  api.generateReceipt(paymentProvider, purchaseId, request);\n} catch (SubscriptionInvalidArgumentsException e) {\n  if (e.getMessage().startsWith(\"unknown payment provider\")) {\n    refreshProviderConfig(); // client/server version mismatch\n  }\n}","preventionTips":["Use only providers the server has registered one-time processors for","Keep client and server payment-provider enums version-aligned","Distinguish login-purchase providers from donation providers in client config"],"tags":["payment-provider","one-time-purchase","argument-validation","unsupported-provider"],"backgroundTag":"invalid-enum-value","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"}