signalapp/Signal-Server · error · SubscriptionInvalidArgumentsException

unknown payment provider:

Error message

unknown payment provider: 

What it means

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.

Solutions

  1. 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).
  2. Verify server configuration registers the oneTimePaymentProcessors map entry for that provider (feature/deployment config).
  3. Align client and server versions so the payment-provider enum matches.
  4. If the provider should be supported, register its OneTimePaymentProcessor implementation at server startup.

Example fix

// before: wrong provider for one-time purchase flow
api.generateReceipt(PaymentProvider.STRIPE, purchaseId, request);
// after
api.generateReceipt(PaymentProvider.APPLE_APP_STORE, purchaseId, request);
Defensive patterns

Strategy: validation

Validate before calling

// keep the client-side allowlist in sync with server-registered processors
const supported = ['APPLE_APP_STORE', 'GOOGLE_PLAY_BILLING'];
if (!supported.includes(paymentProvider.name())) { throw new Error('unsupported provider for one-time purchase'); }

Type guard

function isSupportedOneTimeProvider(p) { return p === 'APPLE_APP_STORE' || p === 'GOOGLE_PLAY_BILLING'; }

Try / catch

try {
  api.generateReceipt(paymentProvider, purchaseId, request);
} catch (SubscriptionInvalidArgumentsException e) {
  if (e.getMessage().startsWith("unknown payment provider")) {
    refreshProviderConfig(); // client/server version mismatch
  }
}

Prevention

When it happens

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

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

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09). Data as JSON: /api/errors/a1f1c92fc52a8fbd. Report an issue: GitHub.

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/LoginPurchaseManager.java:65

  ///
  /// Repeated calls must use the same `receiptCredentialRequest`; a second request for a purchase that was already
  /// redeemed fails with [SubscriptionReceiptAlreadyRedeemedException].
  ///
  /// @param paymentProvider          The provider that processed the purchase
  /// @param purchaseId               The identifier for the purchase in the `paymentProvider`
  /// @param receiptCredentialRequest The request for the receipt to generate. All retries for the same purchaseId must
  /// use the same request
  /// @return The receipt credential
  ///
  public ReceiptCredentialResponse generateReceipt(
      final PaymentProvider paymentProvider,
      final String purchaseId,
      final ReceiptCredentialRequest receiptCredentialRequest)
      throws RateLimitExceededException, SubscriptionInvalidArgumentsException, IOException, SubscriptionNotFoundException, SubscriptionReceiptRequestedForOpenPaymentException, SubscriptionPaymentRequiredException, SubscriptionReceiptAlreadyRedeemedException, VerificationFailedException {

    final OneTimePaymentProcessor oneTimePaymentProcessor = oneTimePaymentProcessors.get(paymentProvider);
    if (oneTimePaymentProcessor == null) {
      throw new SubscriptionInvalidArgumentsException("unknown payment provider: " + paymentProvider);
    }

    final PaymentDetails paymentDetails = oneTimePaymentProcessor
        .claimOneTimePurchase(purchaseId)
        .orElseThrow(SubscriptionNotFoundException::new);
    if (paymentDetails.status() == PaymentStatus.PROCESSING) {
      throw new SubscriptionReceiptRequestedForOpenPaymentException();
    } else if (paymentDetails.status() != PaymentStatus.SUCCEEDED) {
      throw Optional.ofNullable(paymentDetails.chargeFailure())
          .<SubscriptionPaymentRequiredException>map(
              cf -> new SubscriptionChargeFailurePaymentRequiredException(paymentProvider, cf))
          .orElseGet(SubscriptionPaymentRequiredException::new);
    } else if (paymentDetails.level() != ReceiptLevel.LOGIN) {
      throw new SubscriptionInvalidArgumentsException("purchase was for an unexpected product");
    }

    // Calculating the expiration from the creation date works for IAP purchases. However, for other processors, the
    // creation date of the payment intent might be days before the payment actually completed. If we support non-IAP

View on GitHub (pinned to 100ab61c82)