signalapp/Signal-Server · error · BadRequestException
cannot create payment methods with payment type
Error message
cannot create payment methods with payment type ${paymentMethodType} What it means
SubscriptionController.createPaymentMethod throws BadRequestException for GOOGLE_PLAY_BILLING and APPLE_APP_STORE payment method types because those store-backed types cannot create payment methods through the generic donation endpoint — app-store billing is managed by the store. The 400 response echoes the unsupported payment type.
Solutions
- Use the store-specific subscription flow (Google Play Billing / StoreKit) for these payment types
- Send CARD, SEPA_DEBIT, or IDEAL to createPaymentMethod instead
- Use the dedicated create_payment_method/paypal endpoint for PAYPAL
Example fix
// before
{ "paymentMethodType": "GOOGLE_PLAY_BILLING" }
// after
{ "paymentMethodType": "CARD" } Defensive patterns
Strategy: validation
Validate before calling
const GENERIC_TYPES = ['CARD','SEPA_DEBIT','IDEAL'];
if (!GENERIC_TYPES.includes(paymentMethodType)) {
throw new Error(`Use the store-specific flow for ${paymentMethodType}`);
} Type guard
const isGenericPaymentType = (t) => ['CARD','SEPA_DEBIT','IDEAL'].includes(t);
Prevention
- Never route app-store billing through the donation createPaymentMethod endpoint
- Keep a whitelist of generic payment types (CARD, SEPA_DEBIT, IDEAL) for this endpoint
When it happens
Trigger: Calling POST create_payment_method with paymentMethodType GOOGLE_PLAY_BILLING or APPLE_APP_STORE in the request body.
Common situations: Clients reusing the donation createPaymentMethod endpoint for in-app subscription flows handled by Google Play or the App Store; copy-paste of payment type enums across endpoints.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- The PAYPAL payment type must use…
- Invalid payment method
- Operation cannot be performed with the
- 400 Bad Request (INVALID_ARGUMENTS)
- cannot use play billing for one-time donations
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/8d1b93b0dd44b814.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java:290
@Auth Optional<AuthenticatedDevice> authenticatedAccount,
@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)