#ashish_best-practices

1 messages ¡ Page 1 of 1 (latest)

hot dewBOT
#

👋 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.

vagrant flower
#

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?

rustic pollen
#

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*

rustic pollen
#

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 ?

vagrant flower
rustic pollen
#

Thank you

vagrant flower
#

Sure!

rustic pollen
#

last one

#

I do I mock update payment to mock the failed status.

vagrant flower
rustic pollen
#

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.

vagrant flower
#

You can use the card under 'Decline after attaching'

hot dewBOT
rustic pollen
#

Thank you, I will try.

rustic pollen
#

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

summer flare
#

Hello! I'm taking over and catching up...

rustic pollen
#

Or I don't need to pass product detail ?

summer flare
#

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

rustic pollen
#

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 ?

summer flare
#

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.

rustic pollen
#

So basically product detail or id is not required to update the subscription plan

summer flare
#

Yeah, the Price belongs to a Product, so by specifying a Price we can determine what Product it belongs to.

rustic pollen
#

Thank you

summer flare
#

Happy to help!

rustic pollen
#

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

summer flare
#

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.

rustic pollen
#

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"
});

summer flare
#

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.

rustic pollen
#

Got it.

summer flare
#

Glad it's working!

rustic pollen
#

But I see latest invoice is updated.

#

What happens if I leave as it is ?

summer flare
#

Not sure I understand, can you provide more details?

rustic pollen
#

I see now updated invoice with new plan

#

isn't it supposed to be old invoiceid ?

summer flare
#

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.

rustic pollen
#

I mean isn't invoice should be the same ?

#

If customers decide to update payment in a later date ?

summer flare
#

Invoice the same as what?

#

And what does "update payment in a later date" mean?

rustic pollen
#

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 ?

summer flare
#

Meaning if the update fails because the payment fails you want to undo the pending update?

rustic pollen
#

yes, I want to go back to current subscription before update and keep going on this one.

summer flare
#

Did you void the new Invoice as I mentioned above and as the documentation states?

rustic pollen
#

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 ?

summer flare
#

It's earlier in this thread, and in the documentation you linked to earlier.

rustic pollen
#

So after I void invoice how do we revert back the original latest_invoice

summer flare
#

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?

rustic pollen
#

If i want to expand the latest_invoice when fetching the subscription

summer flare
#

Why would you want to expand it?

rustic pollen
#

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
});

summer flare
#

Okay, so why don't you want to show them the latest Invoice which was voided?

rustic pollen
#

Then if I have to show them success invoices then I've to pull the all the success invoices by subscriptionId ?

summer flare
#

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?

rustic pollen
#

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.

summer flare
#

You can test these things yourself in test mode.

rustic pollen
#

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.

summer flare
#

No, that's something you'd need to figure out on your end.

rustic pollen
#

Got it.

summer flare
#

It would be better to listen for Subscription updates with a webhook, but you said without, so no.

rustic pollen
#

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.

summer flare
#

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?

rustic pollen
#

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;
}
}

vagrant flower
#

Let me know what specific questions you have after reading the above

rustic pollen
#

Sure