{"record":{"id":"99c1e02881790737","repo":"signalapp/Signal-Server","slug":"409-conflict","errorCode":null,"errorMessage":"409 Conflict","messagePattern":"409 Conflict","errorType":"http","errorClass":"ClientErrorException","httpStatus":409,"severity":"warning","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java","lineNumber":810,"sourceCode":"  }\n\n  private void setDefaultPaymentMethod(final CustomerAwareSubscriptionPaymentProcessor manager,\n      final String paymentMethodId,\n      final SubscriberCredentials requestData) throws SubscriptionException, IOException {\n    try {\n      final Subscriptions.Record record = subscriptionManager.getSubscriber(requestData);\n\n      final ProcessorCustomer processorCustomer = record.getProcessorCustomer()\n          // a missing customer ID indicates the client made requests out of order,\n          // and needs to call create_payment_method to create a customer for the given payment method\n          .orElseThrow(() ->new ClientErrorException(Status.CONFLICT));\n\n      manager\n          .setDefaultPaymentMethodForCustomer(processorCustomer.customerId(), paymentMethodId, record.subscriptionId);\n    } catch (final SubscriptionInvalidArgumentsException e) {\n      // Here, invalid arguments must mean that the client has made requests out of order, and needs to finish\n      // setting up the paymentMethod first\n      throw new ClientErrorException(Status.CONFLICT);\n    }\n  }\n\n  private String getSubscriptionTemplateId(long level, String currency, PaymentProvider processor) {\n    final SubscriptionLevelConfiguration config = subscriptionConfiguration.getSubscriptionLevel(level);\n    if (config == null) {\n      throw new BadRequestException(Response.status(Status.BAD_REQUEST)\n          .entity(new SetSubscriptionLevelErrorResponse(List.of(\n              new SetSubscriptionLevelErrorResponse.Error(\n                  SetSubscriptionLevelErrorResponse.Error.Type.UNSUPPORTED_LEVEL, null))))\n          .build());\n    }\n    final Optional<String> templateId = Optional\n        .ofNullable(config.prices().get(currency.toLowerCase(Locale.ROOT)))\n        .map(priceConfiguration -> priceConfiguration.processorIds().get(processor));\n    return templateId.orElseThrow(() -> new BadRequestException(Response.status(Status.BAD_REQUEST)\n        .entity(new SetSubscriptionLevelErrorResponse(List.of(\n            new SetSubscriptionLevelErrorResponse.Error(","sourceCodeStart":792,"sourceCodeEnd":828,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java#L792-L828","documentation":"setDefaultPaymentMethod catches SubscriptionInvalidArgumentsException and converts it to an HTTP 409 Conflict: an \"invalid argument\" here means the client issued requests out of order and the payment method (e.g. a Stripe SetupIntent) has not finished setup yet, so it cannot be set as default.","triggerScenarios":"Calling setDefaultPaymentMethod/setDefaultPaymentMethodForIdeal with a paymentMethodId whose setup (redirect confirmation, mandate, SCA) is incomplete.","commonSituations":"IDEAL/bank-debit flows where the client must complete a redirect before attaching; retrying default-payment calls before the webhook/confirmation lands.","solutions":["Complete the payment method setup flow (e.g. IDEAL redirect/confirmation) first, then call setDefaultPaymentMethod","Treat the 409 as a signal to poll/re-fetch the payment method status until setup completes","Re-run the create_payment_method flow if the setup session expired"],"exampleFix":"// before\nawait setDefaultPaymentMethod(token);\n// after\nawait completeIdealRedirect();\nawait waitForPaymentMethodSetup(paymentMethodId);\nawait setDefaultPaymentMethod(token);","handlingStrategy":"retry","validationCode":"// verify the payment method finished setup before making it default\nconst pm = await getPaymentMethod(paymentMethodId);\nif (pm.status !== 'READY') await completeSetupFlow(pm);","typeGuard":null,"tryCatchPattern":"try { await setDefaultPaymentMethod(token); } catch (e) { if (e.status === 409) { await finishPaymentMethodSetup(paymentMethodId); await setDefaultPaymentMethod(token); } else { throw e; } }","preventionTips":["Complete any IDEAL/redirect confirmation before calling setDefaultPaymentMethod","Treat 409 as 'finish setup first', not as a permanent failure"],"tags":["http-409","payments","ideal","out-of-order-requests"],"backgroundTag":"invalid-state-transition","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"}