signalapp/Signal-Server · error · BadRequestException

The PAYPAL payment type must use…

Error message

The PAYPAL payment type must use create_payment_method/paypal

What it means

SubscriptionController.createPaymentMethod throws BadRequestException for the PAYPAL payment type because PayPal must be created through the dedicated create_payment_method/paypal endpoint (processed by Braintree), not the generic switch that routes CARD/SEPA/IDEAL to Stripe.

Solutions

  1. Call POST /create_payment_method/paypal instead of the generic create_payment_method
  2. If Stripe-compatible types are intended, switch the payment type to CARD, SEPA_DEBIT, or IDEAL

Example fix

// before
POST /v1/subscription/create_payment_method  body: {"paymentMethodType":"PAYPAL", ...}
// after
POST /v1/subscription/create_payment_method/paypal  body: {"paymentMethodType":"PAYPAL", ...}
Defensive patterns

Strategy: validation

Validate before calling

if (paymentMethodType === 'PAYPAL') {
  return createPaypalPaymentMethod(args); // use /create_payment_method/paypal
}

Type guard

const isPaypal = (t) => t === 'PAYPAL';

Prevention

When it happens

Trigger: Calling the generic POST create_payment_method endpoint with paymentMethodType PAYPAL instead of the paypal-specific endpoint.

Common situations: Clients constructing a single unified payment-method creation call for all processors; missing the paypal sub-route in the client SDK.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

      @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);
    }

View on GitHub (pinned to 100ab61c82)