#_best-practices

1 messages ยท Page 1 of 1 (latest)

olive brambleBOT
#

๐Ÿ‘‹ Welcome to your new thread!

โฒ๏ธ We'll be here soon! Typically we respond in a few minutes, but sometimes we might take a bit longer if the server is busy or if you have a particularly tricky question.

โฑ๏ธ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can always start a new thread if you have another question.

๐Ÿ”— This thread will always be available, even after it's closed. You can find it again using Discord's search, or you can save this link: https://discord.com/channels/841573134531821608/1484408691959795762

๐Ÿ“ Have more to share? Add more details, code, screenshots, videos, etc. below.

grizzled umbra
#

Hey! Do give me some time to look into this

eternal helm
#

๐Ÿ‘‹ jumping in to help on behalf of Teddy.

I want to make sure I'm understanding your intended flow correctly. When you say you'd use Subscription.update(newPriceId, proration_behavior=none) for downgrades, are you planning to call that at the moment the customer requests the downgrade (mid-cycle), or just prior to the next billing cycle?

hazy palm
#

I want to clarify my question.

We currently implement downgrades by immediately creating a Stripe Subscription Schedule when the customer requests a downgrade. I'd like to know which approach is more recommended between this and simply calling Subscription.update(newPriceId, proration_behavior='none') at the time of the request.

What are the trade-offs, and which would you consider the better practice?

#

I have a follow-up question.

When we need to change pricing for a running service, since Stripe Price objects are immutable, we have to create a new Price. My plan was to update all active subscriptions by calling Subscription.update(newPriceId, proration_behavior='none') for each one โ€” but if there are a large number of active subscriptions, that means a huge number of API calls.

Does Stripe offer any built-in way to bulk-update subscriptions to a new price in one go, rather than looping through them one by one?

eternal helm
#

The reason I was asking about timing is that using Subscription.update() with proration_behavior=none for this use case is a bit tricky, and the "when" really matters.

Here's the dilemma:

If you call the update early (when the customer first requests the downgrade):
The price on the subscription object changes immediately, even though the customer shouldn't be on the new price until next billing. So items[].price will show Starter right away, while the customer should still have Pro-level access for the rest of the current period. This means your application needs to handle that gap. If any of your logic reads the subscription's price for feature gating, entitlements, or even just displaying the current plan to the customer, it'll show the wrong thing unless you build around it. You'd essentially be tracking the "real" current plan separately in your own database until renewal catches up.

If you try to call the update close to renewal:
This is where I'd be concerned about a race condition. If the update lands at or very near the exact moment the subscription renews, you're in uncertain territory. The renewal process generates an invoice and charges the customer, and if your update call and the renewal are happening at roughly the same time, the outcome could be unpredictable.

#

Personally, I'd prefer to stick with Subscription Schedules. One way to reduce the migration risk is to save the schedule details to your own database before releasing. That way if recreation fails, you have everything you need to retry and the downgrade isn't silently lost. It's worth noting that this isn't all that different from what you'd be doing with the Subscription.update() approach anyway, where you'd be tracking the pending downgrade in your own system regardless.

#

I think the best advice I can give here would really be to test everything thoroughly. In case you haven't seen it yet, you can use test clocks to simulate the passing of time, which makes it much easier to validate how things behave across billing cycles without having to wait around.

#

Unfortunately, Stripe doesn't offer a single bulk endpoint that lets you update all subscriptions to a new price in one call. You would need to loop through your active subscriptions and call Subscription.update() on each one individually.

Be mindful of rate limits. While Stripe's overall rate limit is 100 requests per second in live mode, calls to individual resource endpoints have a stricter default limit of 25 requests per second, and these also count against the global limit. So you'll want to build in some throttling and retry logic to stay comfortably within those bounds, especially if you have a large number of subscriptions to update.

hazy palm
#

Following up on the price migration scenario:

If a downgrade is already scheduled via SubscriptionSchedule and an admin changes the plan's price, I'd need to release the existing schedule and recreate it with the new Price ID โ€” which means two separate API calls.

This introduces a partial failure scenario: if the release succeeds but the recreation fails, the scheduled downgrade is silently lost โ€” the subscription just continues on the current plan with no downgrade queued.

Is there any way Stripe handles this atomically, or is this entirely on me to handle in application code (e.g., persisting schedule state before release and retrying on failure)?

grizzled umbra
#

Is there any way Stripe handles this atomically, or is this entirely on me to handle in application code
If making two API calls is a concern, have you considered using the Update Subscription Schedule API call? You should be able to update the schedule to set the new price you want the subscription to downgrade to.

#

That said, my colleagueโ€™s approach is also valid. Which was to persist the Subscription Schedule details in your database and resubmit the creation request if the first attempt fails.

#

This introduces a partial failure scenario:

Hmm, but that partial failure scenario can occur with update Subscription API call as well isnt it?. The update could fail on the first attempt.

olive brambleBOT
hazy palm
#

Actually, I realized the price migration issue could be solved by using the update Subscription Schedule API instead of release + recreate. But I have a related question about another scenario.

I currently handle two schedule transition flows:

  1. User schedules a downgrade โ†’ then schedules a cancellation while the downgrade is still pending: I release the downgrade schedule and create a new cancellation schedule.
  2. User schedules a cancellation โ†’ then schedules a downgrade while the cancellation is still pending: I release the cancellation schedule and create a new downgrade schedule.

Both flows involve the same partial failure risk โ€” the release succeeds but the new schedule creation fails, leaving the subscription with no pending action.

Following your earlier advice, should I apply the same pattern here โ€” persist the current schedule state before releasing, and use that saved state to retry the creation if it fails?