signalapp/Signal-Server · error · BadRequestException

Invalid payment method

Error message

Invalid payment method

What it means

SubscriptionController.createPaymentMethod throws BadRequestException("Invalid payment method") for the UNKNOWN payment type, i.e. the client sent a paymentMethodType that could not be resolved to a known enum member.

Solutions

  1. Check the paymentMethodType string against the current enum values (CARD, SEPA_DEBIT, IDEAL, PAYPAL, GOOGLE_PLAY_BILLING, APPLE_APP_STORE)
  2. Fix typos or casing in the request body
  3. Update the client SDK if the server added payment types the client doesn't know

Example fix

// before
{ "paymentMethodType": "credit_card" }
// after
{ "paymentMethodType": "CARD" }
Defensive patterns

Strategy: validation

Validate before calling

const VALID = ['CARD','SEPA_DEBIT','IDEAL','PAYPAL','GOOGLE_PLAY_BILLING','APPLE_APP_STORE'];
if (!VALID.includes(paymentMethodType)) throw new Error(`Unknown paymentMethodType: ${paymentMethodType}`);

Type guard

const isKnownPaymentType = (t) => ['CARD','SEPA_DEBIT','IDEAL','PAYPAL','GOOGLE_PLAY_BILLING','APPLE_APP_STORE'].includes(t);

Prevention

When it happens

Trigger: POSTing create_payment_method with a paymentMethodType string that deserializes to PaymentMethodType.UNKNOWN (unrecognized/misspelled value or empty field).

Common situations: Typos in the payment type string; client SDK older than newly added enum values; serialization sending null/missing field that maps to UNKNOWN.

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/4786ecb9e8b7f0da. Report an issue: GitHub.

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java:292

      @Parameter(description="A base64-encoded donation permit retrieved from POST /v1/donation/permit")
      @HeaderParam(HeaderUtils.DONATION_PERMIT)
      final Optional<DonationPermitHeader> donationPermitHeader,

      @PathParam("subscriberId") String subscriberId,
      @QueryParam("type") @DefaultValue("CARD") PaymentMethod paymentMethodType,
      @HeaderParam(HttpHeaders.USER_AGENT) @Nullable final String userAgentString) throws SubscriptionException {

    SubscriberCredentials subscriberCredentials =
        SubscriberCredentials.process(authenticatedAccount, subscriberId, clock);

    final CustomerAwareSubscriptionPaymentProcessor customerAwareSubscriptionPaymentProcessor = switch (paymentMethodType) {
      // Today, we always choose stripe to process non-paypal payment types, however we could use braintree to process
      // other types (like CARD) in the future.
      case CARD, SEPA_DEBIT, IDEAL -> stripeManager;
      case GOOGLE_PLAY_BILLING, APPLE_APP_STORE ->
          throw new BadRequestException("cannot create payment methods with payment type " + paymentMethodType);
      case PAYPAL -> throw new BadRequestException("The PAYPAL payment type must use create_payment_method/paypal");
      case UNKNOWN -> throw new BadRequestException("Invalid payment method");
    };

    SubscriptionsUtil.recordDonationPermitPresent(donationPermitHeader.isPresent(), "createPaymentMethod", userAgentString);
    final boolean spendSuccessful = donationPermitHeader.map(
            permitHeader -> {
              try {
                return SubscriptionsUtil.verifyAndSpendDonationPermit(permitHeader.permit(), donationPermitsManager, clock);
              } catch (VerificationFailedException e) {
                return false;
              }
            })
        .orElse(false);

    if (!spendSuccessful) {
      throw new WebApplicationException(Response.Status.UNAUTHORIZED);
    }

    final String token = subscriptionManager.addPaymentMethodToCustomer(

View on GitHub (pinned to 100ab61c82)