Skip to content

Surface PREVIOUS_PAYMENT_PENDING from subscriptions.changePlan as a distinct exception type (and document the constraint) #88

Description

@willedwards

Summary

Calling subscriptions.changePlan shortly after a previous changePlan (before its prorated charge has settled) causes Dodo to return a 409 with this body:

{ "code": "PREVIOUS_PAYMENT_PENDING", "message": "Cannot change plan as previous payment is not successful yet" }

The Java SDK currently surfaces this as a generic DodoPaymentsException, identical in type to a permanently-rejected card. Consumers can't programmatically distinguish "wait
and retry" from "the card was declined for real" without parsing the error message string, which is fragile.

This issue requests two things:

  1. A typed exception (e.g. PreviousPaymentPendingException or PaymentInFlightException) for this specific code, so callers can implement appropriate retry-with-backoff logic
    and surface a meaningful error to end users.
  2. A short note in the docs for subscriptions.changePlan calling out that change-plan calls cannot overlap with a prior in-flight prorated charge — currently the constraint is
    undocumented and only discoverable by hitting the 409 in production.

Reproduction

Sequential calls in test mode, ~1 second apart, against the same subscription:

// First call: succeeds, fires a prorated charge.
sdk.subscriptions().changePlan(SubscriptionChangePlanParams.builder()
.subscriptionId("sub_xxx")
.updateSubscriptionPlanReq(UpdateSubscriptionPlanReq.builder()
.productId("prod_xxx")
.quantity(1)
.prorationBillingMode(ProrationBillingMode.PRORATED_IMMEDIATELY)
.addAddon(AttachAddon.builder().addonId("adn_seats").quantity(3).build())
.build())
.build());

Thread.sleep(1000);

// Second call: throws DodoPaymentsException with the 409 body above.
sdk.subscriptions().changePlan(SubscriptionChangePlanParams.builder()
.subscriptionId("sub_xxx")
// … same shape, quantity 2 …
.build());

Why this matters

In our use case (a SaaS adding/removing per-client seats), it's plausible for an admin to upgrade their seat count and within a few minutes realise they want one fewer. With
the current SDK behaviour we'd have to inspect the exception's response body string (code=PREVIOUS_PAYMENT_PENDING) to give them an accurate message ("your previous change is
still processing — try again in a moment") rather than the misleading default ("card declined"). A typed exception would make this clean.

Environment

  • com.dodopayments:dodopayments-java:1.86.3
  • Java 17, Spring Boot 3.2.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions