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
- 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.
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
- 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
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
- transaction is not a consumable one-time purchase
- purchase was for an unexpected product
- Operation cannot be performed with the
- transactionId did not include a paid subscription or the…
- result.getMessage()
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-IAPView on GitHub (pinned to 100ab61c82)