#ashish_best-practices
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/1314625035859918878
đ Have more to share? Add more details, code, screenshots, videos, etc. below.
Below are links to other discussions we've had with you in the past week in case you want to review that information. If your question is related to one of these previous discussions, please provide a comprehensive summary of the current state and what you need help with now. We help many users simultaneously, so a summary allows us to resolve your issue as soon as possible.
- ashish_best-practices, 18 hours ago, 14 messages
- ashish_api, 1 day ago, 7 messages
- ashish_best-practices, 3 days ago, 9 messages
- ashish_best-practices, 3 days ago, 4 messages
- ashish_best-practices, 3 days ago, 13 messages
Hi, can you reqorw the question here? Also, can you share oject ids and explain what you expect to see vs, what you're seeing?
I don't have implemented yet. But I am planning to update subscription.
async function updateSubscriptionItem(subscriptionId, productId, newPriceId) {
try {
const updatedSubscription = await stripe.subscriptions.update(subscriptionId, {
items: [
{
id: productId, // Replace the existing subscription item
price: newPriceId, // New price ID
},
],
billing_cycle_anchor: "now",
proration_behavior: "always_invoice",
});
return updatedSubscription;
} catch (error) {
console.error("Error updating subscription:", error.message);
throw error;
}
Now for some reason if payment didn't fails, for example insuffient fund, I want to revert back to old subscription keeping old plan and pricing and old billing cycle.
I mean if payment fails*
This seems like how to handle failure, but use case is to revert back at first attempt fail.
for example they want to switch to monthly to yearly and no sufficient fund rejection.
I want to revert back to old subscription using below code.
const stripe = require('stripe')('your_stripe_secret_key');
async function updateSubscriptionWithFallback(subscriptionId, oldPriceId, newPriceId, itemId) {
try {
// Step 1: Retrieve the current subscription to get the billing_cycle_anchor
const currentSubscription = await stripe.subscriptions.retrieve(subscriptionId);
const originalBillingCycleAnchor = currentSubscription.billing_cycle_anchor;
// Step 2: Attempt to update the subscription to the new plan
const updatedSubscription = await stripe.subscriptions.update(subscriptionId, {
items: [
{
id: itemId, // Subscription item ID
price: newPriceId, // New price ID
},
],
billing_cycle_anchor: 'now', // Temporarily reset to "now" for testing payment
proration_behavior: 'always_invoice', // Immediate charge
});
console.log('Updated subscription:', updatedSubscription);
// Step 3: Check the payment status of the latest invoice
const latestInvoice = await stripe.invoices.retrieve(updatedSubscription.latest_invoice);
if (latestInvoice.status === 'paid') {
console.log('Payment succeeded. Subscription updated to the new plan.');
return updatedSubscription;
} else {
console.warn('Payment failed. Reverting to the old plan...');
// Step 4: Revert to the old plan with the original billing_cycle_anchor
const revertedSubscription = await stripe.subscriptions.update(subscriptionId, {
items: [
{
id: itemId, // Subscription item ID
price: oldPriceId, // Old price ID
},
],
billing_cycle_anchor: originalBillingCycleAnchor, // Use the original billing cycle
proration_behavior: 'none', // Avoid additional prorations during rollback
});
console.log('Reverted to old plan:', revertedSubscription);
return revertedSubscription;
}
} catch (error) {
console.error('Error handling subscription update:', error.message);
throw error;
}
}
Does it sound right ?
Yes, what I shared above is what you want, https://docs.stripe.com/billing/subscriptions/pending-updates#handling-failed-payments
I recommend that you just test this and confirm it's what you'd want
Thank you
Sure!
You can use our test decline cards to test payment failures: https://docs.stripe.com/testing?testing-method=card-numbers&payment-method=others#declined-payments
I am using existing payment method to bill the subscription. When I try to use test decline cards, it's not adding as payment method, hence I am unable to test subscription update for failure due to card declined.
You can use the card under 'Decline after attaching'
Thank you, I will try.
When I try to update using below code
const updatedSubscription = await stripe.subscriptions.update(subscriptionId, {
items: [
{
id: newProductId, // Replace the existing subscription item
price: newPriceId, // New price ID
},
],
billing_cycle_anchor: "now",
proration_behavior: "always_invoice",
});
I get following error
raw: {
message: 'No subscription item with this ID (prod_RIqS9TY5jtByOf) on the subscription.',
request_log_url: 'https://dashboard.stripe.com/test/logs/req_eA2Dtvulrb03zx?t=1733504512',
type: 'invalid_request_error',
I want to replace both plan product and price in subscription. Not just pricing
Hello! I'm taking over and catching up...
Or I don't need to pass product detail ?
The id in items needs to be the Subscription Item ID you want to update,. not the Product ID. The ID you want is the existing si_ ID on the Subscription in question.
I think you had it correct earlier: id: itemId, // Subscription item ID
const subscription = await stripe.subscriptions.retrieve(subscriptionId);
if (!subscription) {
result.error = "SubscriptionNotFoundStripe";
result.message = "Subscription not found on stripe";
return result;
}
const subscriptionItemId = subscription?.items?.data?.[0]?.id; // Get the first subscription item's ID
const updatedSubscription = await stripe.subscriptions.update(subscriptionId, {
items: [
{
id: subscriptionItemId, // Replace the existing subscription item
price: newPriceId, // New price ID
},
],
billing_cycle_anchor: "now",
proration_behavior: "always_invoice",
});
Like this ?
Assuming itemId is the si_ ID of the Subscription Item you want to change.
Yeah, that works too.
Or it should, anyway, assuming it's the first one.
So basically product detail or id is not required to update the subscription plan
Yeah, the Price belongs to a Product, so by specifying a Price we can determine what Product it belongs to.
Thank you
Happy to help!
Here again
After updating the subcription with payment_method decline card.
status is past_due
How can I change the retry strategy for this subscription when updating that it can try once and populate pending_update
For testing
https://dashboard.stripe.com/test/subscriptions/sub_1QSWHPKIBj7DgPMXfpAt9jhp
I updated the the subscription with new price and failed credit card after attach.
According to this
https://docs.stripe.com/billing/subscriptions/pending-updates#handling-failed-payments
it should return me pending_update
That documentation only applies if you set payment_behavior to pending_if_incomplete in the update request, which it doesn't look like you're doing based on the code you shared above.
Like this ?
const updatedSubscription = await stripe.subscriptions.update(subscriptionId, {
items: [
{
id: subscriptionItemId, // Replace the existing subscription item
price: newPriceId, // New price ID
},
],
billing_cycle_anchor: "now",
proration_behavior: "always_invoice",
payment_behavior: "pending_if_incomplete"
});
We can't tell you what your code is going to do when you run it, or verify every change. You should try this in test mode and see if it works or not.
We're happy to help if you get stuck or have specific questions, but we can't write, run, or review your code for you, that's something you need to do on your end.
Got it.
Seems like https://dashboard.stripe.com/test/logs/req_LOiLX51nFKMAy5
Now it looks good
Glad it's working!
Not sure I understand, can you provide more details?
No. A new Invoice is created for the Customer to pay.
From the documentation you linked to above:
Use the instructions for payment failures to handle card declines. You need to attach a new payment method to the customer and then use the pay endpoint to pay the invoice that the update generates.
What's your goal? Why were you not expecting/wanting an Invoice to be generated?
If you want to cancel the pending update after the initial payment failure you can void the Invoice.
I mean isn't invoice should be the same ?
If customers decide to update payment in a later date ?
So current subscription has latest invoice = in_1QSXsDKIBj7DgPMXShO8oF2n
When I update the subscirption with failed payment method it populated pending_update
and also update the latest_invoice to new one.
What I want is to continue using current subscription with current billing amount and duration.
when the next billing period end comes, will it generate the new invoice ?
Meaning if the update fails because the payment fails you want to undo the pending update?
yes, I want to go back to current subscription before update and keep going on this one.
Did you void the new Invoice as I mentioned above and as the documentation states?
Because if user switch back to bigger yearly plan, their card denied due to insufficient fund
"Did you void the new Invoice as I mentioned above and as the documentation states?
No, where is that ?
So after I void invoice how do we revert back the original latest_invoice
You don't. The voided Invoice is still the latest Invoice at that point. Why do you want to change the latest Invoice to something that isn't the latest Invoice?
If i want to expand the latest_invoice when fetching the subscription
Why would you want to expand it?
In our user UI end, they can see the list of subscription and latest invoice from subscription.
const subscription = await stripe.subscriptions.retrieve(subscriptionId, {
expand: ['latest_invoice'], // Expand the latest_invoice field
});
Okay, so why don't you want to show them the latest Invoice which was voided?
Then if I have to show them success invoices then I've to pull the all the success invoices by subscriptionId ?
To clarify, the answer is that you don't want to show them the latest Invoice, right? You actually want to show them the last successfully paid Invoice for that Subscription?
If that's the case you can list Invoices by subscription and status and limit the results to a single Invoice to get the latest paid Invoice: https://docs.stripe.com/api/invoices/list
Got it. Thanks.
last one,
What happens to pending_update object after I void invoice ?
I alse see expires_at value on pending_updates. what happens after expires_at time has reached.
You can test these things yourself in test mode.
Note that you can advance time in test mode using test clocks: https://docs.stripe.com/billing/testing/test-clocks
Thank you
Similar topic but for subscription.
Lets say I retrieve the subscription by id last week and saved into local database, Now I retrieve again, is there way to check easily like hash attribute or something to check if the last fetch and current fetch has changes.
Without webhook.
No, that's something you'd need to figure out on your end.
Got it.
It would be better to listen for Subscription updates with a webhook, but you said without, so no.
You could list Events and look for the relevant update Events and parse through them, I suppose: https://docs.stripe.com/api/events/list
Gotcha. Thanks again.
I have no choice but to handle with webhook is the best approach.
one more time.
Is there way to grab the reason being payment failed with testing card decline after attach. I want to save the payment failed reason as well.
You need to provide more specific details. What does "after attach" mean? Can you point me to an example request ID showing this in action?
So
After failed payment with updating subscription due to declined card, I want to save the reason payment was denied. Sometime it might be expired, insufficient fund.
Code snippet for my logic
async function updateSubscriptionItem(subscriptionId, newPriceId) {
const result = {
subscription: null,
error: null,
message: null,
};
try {
// Check if subscription exist
const subscription = await stripe.subscriptions.retrieve(subscriptionId);
if (!subscription) {
result.error = "SubscriptionNotFoundStripe";
result.message = "Subscription not found on stripe";
return result;
}
const subscriptionItemId = subscription?.items?.data?.[0]?.id; // Get the first subscription item's ID
// Check if price exist
const price = await stripe.prices.retrieve(newPriceId);
if (!price) {
result.error = "PriceNotFoundStripe";
result.message = "Price not found on stripe";
return result;
}
const updatedSubscription = await stripe.subscriptions.update(subscriptionId, {
items: [
{
id: subscriptionItemId, // Replace the existing subscription item
price: newPriceId, // New price ID
},
],
billing_cycle_anchor: "now",
proration_behavior: "always_invoice",
payment_behavior: "pending_if_incomplete",
});
if (updatedSubscription?.pending_update) {
const invoiceId = updatedSubscription?.latest_invoice;
const invoice = await stripe.invoices.retrieve(invoiceId, {
expand: ["payment_intent"], // Expand the payment_intent field
});
await stripe.invoices.voidInvoice(invoiceId);
result.error = "SubscriptionNotFoundStripe";
result.message = "Subscription not found on stripe";
}
result.subscription = updatedSubscription;
return result;
} catch (error) {
console.error("Error updating subscription:", error.message);
throw error;
}
}
You might want to review this: https://docs.stripe.com/declines.
Let me know what specific questions you have after reading the above
Sure