{"record":{"id":"8d1b93b0dd44b814","repo":"signalapp/Signal-Server","slug":"cannot-create-payment-methods-with-payment-type","errorCode":null,"errorMessage":"cannot create payment methods with payment type ${paymentMethodType}","messagePattern":"cannot create payment methods with payment type (.+?)","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java","lineNumber":290,"sourceCode":"      @Auth Optional<AuthenticatedDevice> authenticatedAccount,\n\n      @Parameter(description=\"A base64-encoded donation permit retrieved from POST /v1/donation/permit\")\n      @HeaderParam(HeaderUtils.DONATION_PERMIT)\n      final Optional<DonationPermitHeader> donationPermitHeader,\n\n      @PathParam(\"subscriberId\") String subscriberId,\n      @QueryParam(\"type\") @DefaultValue(\"CARD\") PaymentMethod paymentMethodType,\n      @HeaderParam(HttpHeaders.USER_AGENT) @Nullable final String userAgentString) throws SubscriptionException {\n\n    SubscriberCredentials subscriberCredentials =\n        SubscriberCredentials.process(authenticatedAccount, subscriberId, clock);\n\n    final CustomerAwareSubscriptionPaymentProcessor customerAwareSubscriptionPaymentProcessor = switch (paymentMethodType) {\n      // Today, we always choose stripe to process non-paypal payment types, however we could use braintree to process\n      // other types (like CARD) in the future.\n      case CARD, SEPA_DEBIT, IDEAL -> stripeManager;\n      case GOOGLE_PLAY_BILLING, APPLE_APP_STORE ->\n          throw new BadRequestException(\"cannot create payment methods with payment type \" + paymentMethodType);\n      case PAYPAL -> throw new BadRequestException(\"The PAYPAL payment type must use create_payment_method/paypal\");\n      case UNKNOWN -> throw new BadRequestException(\"Invalid payment method\");\n    };\n\n    SubscriptionsUtil.recordDonationPermitPresent(donationPermitHeader.isPresent(), \"createPaymentMethod\", userAgentString);\n    final boolean spendSuccessful = donationPermitHeader.map(\n            permitHeader -> {\n              try {\n                return SubscriptionsUtil.verifyAndSpendDonationPermit(permitHeader.permit(), donationPermitsManager, clock);\n              } catch (VerificationFailedException e) {\n                return false;\n              }\n            })\n        .orElse(false);\n\n    if (!spendSuccessful) {\n      throw new WebApplicationException(Response.Status.UNAUTHORIZED);\n    }","sourceCodeStart":272,"sourceCodeEnd":308,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java#L272-L308","documentation":"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.","triggerScenarios":"Calling POST create_payment_method with paymentMethodType GOOGLE_PLAY_BILLING or APPLE_APP_STORE in the request body.","commonSituations":"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.","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"],"exampleFix":"// before\n{ \"paymentMethodType\": \"GOOGLE_PLAY_BILLING\" }\n// after\n{ \"paymentMethodType\": \"CARD\" }","handlingStrategy":"validation","validationCode":"const GENERIC_TYPES = ['CARD','SEPA_DEBIT','IDEAL'];\nif (!GENERIC_TYPES.includes(paymentMethodType)) {\n  throw new Error(`Use the store-specific flow for ${paymentMethodType}`);\n}","typeGuard":"const isGenericPaymentType = (t) => ['CARD','SEPA_DEBIT','IDEAL'].includes(t);","tryCatchPattern":null,"preventionTips":["Never route app-store billing through the donation createPaymentMethod endpoint","Keep a whitelist of generic payment types (CARD, SEPA_DEBIT, IDEAL) for this endpoint"],"tags":["http-400","bad-request","payments","subscriptions"],"backgroundTag":"unsupported-operation","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}