signalapp/Signal-Server · error · BadRequestException
cannot use play billing for one-time donations
Error message
cannot use play billing for one-time donations
What it means
App-store and Play Billing purchases cannot be redeemed as one-time boost donations; the receipt-credentials endpoint rejects those processors with HTTP 400. Only STRIPE and BRAINTREE processors are accepted for boosts.
Solutions
- Use STRIPE or BRAINTREE as the processor for one-time donations
- Submit Play Billing / App Store purchase tokens through the subscription in-app-purchase endpoints instead
- Fix the client enum so boost flows map to card/PayPal processors only
Example fix
// before
{ "processor": "GOOGLE_PLAY_BILLING", "paymentIntentId": "..." }
// after
{ "processor": "STRIPE", "paymentIntentId": "..." } Defensive patterns
Strategy: validation
Validate before calling
const ALLOWED = ['STRIPE', 'BRAINTREE'];
if (!ALLOWED.includes(request.processor)) throw new Error(`boosts do not support processor ${request.processor}`); Type guard
function supportsBoosts(p) { return p === 'STRIPE' || p === 'BRAINTREE'; } Try / catch
try { await createBoostReceiptCredentials(req); } catch (e) { if (e.status === 400 && /play billing|app store/.test(e.message)) routeToSubscriptionIapFlow(req); else throw e; } Prevention
- Only send STRIPE or BRAINTREE for one-time donations
- Route Play Billing / App Store receipts to subscription IAP endpoints
- Constrain the processor field to the supported enum in the client type definitions
When it happens
Trigger: Passing processor=GOOGLE_PLAY_BILLING (or APPLE_APP_STORE) in the CreateBoostReceiptCredentialsRequest to the boost receipt-credentials endpoint.
Common situations: Clients reusing the donation receipt-credential flow (meant for subscriptions) for in-app purchase receipts; wrong enum value selected when building the request.
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
- cannot use app store purchases for one-time donations
- receipt credential request failed verification
- cannot create payment methods with payment type
- The PAYPAL payment type must use…
- Invalid payment method
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/f044eb97e95d84ab.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/OneTimeDonationController.java:355
@POST
@Path("/receipt_credentials")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@ManagedAsync
public Response createBoostReceiptCredentials(
@Auth final Optional<AuthenticatedDevice> authenticatedAccount,
@NotNull @Valid final CreateBoostReceiptCredentialsRequest request,
@HeaderParam(HttpHeaders.USER_AGENT) final String userAgent) throws IOException {
if (authenticatedAccount.isPresent()) {
throw new ForbiddenException("must not use authenticated connection for one-time donation operations");
}
final Optional<PaymentDetails> maybePaymentDetails = (switch (request.processor) {
case STRIPE -> stripeManager.claimOneTimePurchase(request.paymentIntentId);
case BRAINTREE -> braintreeManager.claimOneTimePurchase(request.paymentIntentId);
case GOOGLE_PLAY_BILLING -> throw new BadRequestException("cannot use play billing for one-time donations");
case APPLE_APP_STORE -> throw new BadRequestException("cannot use app store purchases for one-time donations");
});
if (maybePaymentDetails.isEmpty()) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
final PaymentDetails paymentDetails = maybePaymentDetails.get();
if (paymentDetails.status() == PaymentStatus.PROCESSING) {
return Response.noContent().build();
}
if (paymentDetails.status() != PaymentStatus.SUCCEEDED) {
throw new WebApplicationException(Response.status(Response.Status.PAYMENT_REQUIRED)
.entity(new CreateBoostReceiptCredentialsErrorResponse(paymentDetails.chargeFailure())).build());
}
// The payment was successful, try to issue the receipt credential
final OneTimeDonationUtil.DonationLevelDetails levelDetails;View on GitHub (pinned to 100ab61c82)