#chip_code
1 messages ยท Page 1 of 1 (latest)
๐ 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.
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
Hello, do you have the ID of a subscription that you saw this with? (sub_1234)
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
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
Yes, this is the only func for updating the subscription. We have a worker that updates the usage with "set"
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
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.
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?
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
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
Yeah!
sub_1QltkHKOJjwbkhwz0zWzEYgr
Let me know if you want a bit "cleaner" subscription ๐
that one got a couple updates back and forth
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?
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?
Because I have no clue where #1333511643942092912 message 557 comes from ๐
ah
so it does add it all up?
What could cause that? ๐ค
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?
I thought so ๐ but looking at this example here I did in test happened, right? https://cdn.discordapp.com/attachments/1333511643942092912/1333518216512802826/Screenshot_2025-01-27_at_20.25.13.png?ex=67992f1e&is=6797dd9e&hm=bc5ff766608e42077a43de2e97f3e2471e60ac34f1884950b875873189fe8eb1& ?
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
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.
oooo
๐ค
That's probably why
if I'm understanding that right
should be this
Exactly. So you will need to create a new Price that is the same but uses that aggregation method
Yeah! Anything specific to think about when I update this tiered price for all the customers?
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.
Alright, thank you so much @gilded pike! I'll go ahead and start writing up a script for it ๐