#chip_code

1 messages ยท Page 1 of 1 (latest)

vivid forgeBOT
#

๐Ÿ‘‹ 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/1333511643942092912

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

modest lichen
#
  const updatedItems: Stripe.SubscriptionUpdateParams.Item[] = [];

  currentSubscription.items.data.forEach((item) => {
    const matchingNewPrice = newPrices.find(
      (price) =>
        price.recurring?.usage_type === item.price.recurring?.usage_type &&
        price.recurring?.interval === item.price.recurring?.interval,
    );

    if (matchingNewPrice) {
      updatedItems.push({ id: item.id, price: matchingNewPrice.id });
      newPrices.splice(newPrices.indexOf(matchingNewPrice), 1);
    } else {
      updatedItems.push({ id: item.id, deleted: true });
    }
  });

  newPrices.forEach((price) => {
    updatedItems.push({ price: price.id });
  });

  if (updatedItems.every((item) => item.deleted)) {
    throw new Error(
      "Cannot remove all subscription items. At least one item must remain.",
    );
  }

  return await stripe.subscriptions.update(currentSubscription.id, {
    items: updatedItems,
    proration_behavior: "none",
  });
#

This is the main part

#

Not the greatest code, I'm aware, I'm just trying to figure out the specifics of prorations works here

#

I got some screenshots as well

#

gimmie a sec

gilded pike
#

Hello, do you have the ID of a subscription that you saw this with? (sub_1234)

modest lichen
#

This is the current sub for one of our customer: sub_1PDsUnKOJjwbkhwzAbVt7UoM

#

This is what it looks like when they upgrade to our Pro plan

#

This should be the eventID: evt_1QlxICKOJjwbkhwzmTBnxeLp when they updated

gilded pike
#

Thank you for the example. And just to make sure I am on the same page: as best you can tell the only call that is made here is the update call and then there is all that extra usage on that customer? Looking into this on our side

modest lichen
#
export async function updateSubscription(_userId: string, priceIds: string[]) {
  const user = await auth();
  if (!user) throw new Error("User not found");

  const currentSubscription = await getSubscription(user.user);
  if (!currentSubscription) throw new Error("Subscription not found");

  const newPrices = await Promise.all(
    priceIds.map((id) => stripe.prices.retrieve(id)),
  );

  const flatPrices = newPrices.filter(
    (price) => price.recurring?.usage_type !== "metered",
  );

  const flatPriceIntervals = new Set(
    flatPrices.map((price) => price.recurring?.interval),
  );

  if (flatPriceIntervals.size > 1) {
    throw new Error("Cannot combine flat prices with different intervals");
  }

  const updatedItems: Stripe.SubscriptionUpdateParams.Item[] = [];

  currentSubscription.items.data.forEach((item) => {
    const matchingNewPrice = newPrices.find(
      (price) =>
        price.recurring?.usage_type === item.price.recurring?.usage_type &&
        price.recurring?.interval === item.price.recurring?.interval,
    );

    if (matchingNewPrice) {
      updatedItems.push({ id: item.id, price: matchingNewPrice.id });
      newPrices.splice(newPrices.indexOf(matchingNewPrice), 1);
    } else {
      updatedItems.push({ id: item.id, deleted: true });
    }
  });

  newPrices.forEach((price) => {
    updatedItems.push({ price: price.id });
  });

  if (updatedItems.every((item) => item.deleted)) {
    throw new Error(
      "Cannot remove all subscription items. At least one item must remain.",
    );
  }

  return await stripe.subscriptions.update(currentSubscription.id, {
    items: updatedItems,
    proration_behavior: "none",
  });
}

this is the entire function if that's helpful

#

I'm gonna try to do a repro in test mode locally here now

gilded pike
#

Sounds good, let me know what you can reproduce. I'm having trouble seeing exactly what happened here. I am reaching out to my colleagues and will get back with what we can find.

modest lichen
#

Sweet! I'll get back to you once I got something ๐Ÿ‘

#

Alright, so here I have my first usage record

#

I'll try advancing the time, get some more storage and upgrading ๐Ÿค”

#

upgraded to Pro

#

and since it's below 120 there's no overage

#

and so is that first example with our customer. They have only 98 in usage total

#

Is it because there's some existing proration on their subscription this billing cycle?

gilded pike
#

Not sure, proration isn't supposed to effect usage-based prices. And is that 40 generated entirely by the update call and not you reporting 40 usage on that customer?

#

Also can you send the ID of the test subscription? Could be useful to have as well

modest lichen
#
func updateStripeUsage(subscriptionItemID string, totalStorage int64) error {
    _, err := usagerecord.New(&stripe.UsageRecordParams{
        Quantity:         stripe.Int64(totalStorage),
        SubscriptionItem: stripe.String(subscriptionItemID),
        Action:           stripe.String(string(stripe.UsageRecordActionSet)),
    })
    return err
}

this is usage update code

modest lichen
#

sub_1QltkHKOJjwbkhwz0zWzEYgr

#

Let me know if you want a bit "cleaner" subscription ๐Ÿ˜„

#

that one got a couple updates back and forth

gilded pike
#

Oh I may have misunderstood, so after the upgrade you are trying to update existing usage but instead of updating the existing usage amount, it is getting added to?

modest lichen
#

No, so we only run the usage update every 24hrs. I would've expected that the usage of 98 (GB in this case) would stay when our customer updates, but it seems like it almost adds up all the usage records and totals it?

#

so it does add it all up?

#

What could cause that? ๐Ÿค”

gilded pike
#

That is what I am trying to figure out, I do see that the price is configured to use the last reported usage, so it is surprising that this sum behavior is happening.

#

And as far as you've seen these prices seem to behave properly with using that last reported price otherwise? Like if you start a subscription on these prices and report the usage does the price behave properly?

modest lichen
#

I had 44 as my last usage record

#

then there is the 20 for free on hobby

#

well

#

Maybe my mental model of this is f'd

#

Here I'd expect to only see 40 and not 64, that's what I mean

gilded pike
#

Yeah that reproduced it. I think you have it right, with the right config we should just use the latest usage number that you reported. I am trying to figure out if this is a bug.

modest lichen
#

oooo

#

๐Ÿค”

#

That's probably why

#

if I'm understanding that right

#

should be this

gilded pike
#

Exactly. So you will need to create a new Price that is the same but uses that aggregation method

modest lichen
#

Yeah! Anything specific to think about when I update this tiered price for all the customers?

gilded pike
#

Thinking on that but nothing comes to mind immediately. Definitely test in test mode first but I think if you make that update with proration_behavior: 'none' it shouldn't have any effect other than how the usage is calculated.

modest lichen
#

Alright, thank you so much @gilded pike! I'll go ahead and start writing up a script for it ๐Ÿ‘