#monarch_unexpected
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/1372822515193221120
📝 Have more to share? Add more details, code, screenshots, videos, etc. below.
Hi there, I don't understand the question. Can you rephrase?
Hey, sure, just a min
I am in the process of building a subscription system that operates as follows: when a user initially signs up, I create a customer profile and assign them a subscription with a $0 cost. After the user logs in, they have the option to upgrade their subscription to one of two paid plans: $49 or $249.
To handle the payment flow, I am utilizing React’s payment elements to display the payment form and invoice. I rely on the payment_succeeded event in webhooks to trigger the subscription upgrade process. This setup works fine when a user completes the payment.
However, I've encountered an issue when a user attempts to upgrade their plan. The payment form appears with an option to cancel the upgrade. If the user decides to cancel, the current invoice is marked as void, which seems to be functioning as expected.
The problem arises when the user tries to upgrade again after canceling. In this case, the system automatically updates the subscription plan without prompting the user to make a new payment, due to "unused time" from the previous subscription. I have attached the invoice for reference. This is not the behavior I intend to have.
My question is: Is this behavior expected based on the current flow I’ve implemented? Or do I need to adjust the process in some way to ensure that the subscription plan is only upgraded after a successful payment, regardless of any unused time left on the previous plan?
My react code, if it helps
My express code, if it helps
Ok, I think I understand you now. You want to avoid the prorated credits added to the customer as a result of subscription cancellation? Am I right?
Can you share with me the code that you wrote to cancel a subscription?
Yes
req: Request,
res: Response,
): Promise<void> => {
const { id } = req.params;
console.log(
`Controller: voidInvoiceHandler called for ID: ${id} with body:`,
req.body,
);
try {
const params: Stripe.InvoiceVoidInvoiceParams = req.body; // Body might be empty
const invoice = await stripeService.voidInvoice(id, params);
res.status(200).json(invoice);
} catch (error: any) {
console.error(`Error in voidInvoiceHandler for ID ${id}:`, error);
res.status(error.statusCode || 500).json({
message: "Failed to void invoice",
error: error.message,
});
}
}; ```
async voidInvoice(
invoiceId: string,
params?: Stripe.InvoiceVoidInvoiceParams,
): Promise<Stripe.Invoice> {
return this.stripe.invoices.voidInvoice(invoiceId, params);
}
I use this code to void the invoice not technically cancelling the subscription itself, that is used in downgrades, which works fine
This is just to void the invoice, what triggers the subscription cancellation?
export const cancelSubscription = async (
req: Request,
res: Response,
): Promise<void> => {
console.log("Controller: cancelSubscription called for ID:", req.params.id);
try {
const { id } = req.params;
const subscription = await stripeService.cancelSubscription(id);
res.status(200).json(subscription);
} catch (error: any) {
console.error("Error in cancelSubscription:", error);
res.status(500).json({
message: "Failed to cancel subscription",
error: error.message,
});
}
};
I only use this for downgrades
When I say cancel I mean a cancel button on my payment form, that just voids the invoice, since user did not go through the payment
Ok, can you share with me an ID of an sample subscription that was canceled ?
sub_1RPFoGQ5lokX8cUNsVrJZSpZ
Also I am using cancelSubscription to donwgrade subscription which works fine
This is what is happening
when I click the cancel button the voidInvoice logic is called
Hmm, the subscription that you provided is still active
yes but I am not cancelling the subscription
I am just voiding the invoice
that is what i want, you can watch the video to understand better
Thanks for the video/
https://docs.stripe.com/billing/subscriptions/pending-updates you'll want to use pending update feture so that the subscription is only updated when the payment succeeds. And you don't need to void invoices which resulted in unexpected credits
What to do when user clicks cancel button just update ui ? no changes/requests in backend ?
hi! I'm taking over this thread.
can you clarify your question? what exactly are you trying to achieve here?
Hey, sure, just a min
I am in the process of building a subscription system that operates as follows: when a user initially signs up, I create a customer profile and assign them a subscription with a $0 cost. After the user logs in, they have the option to upgrade their subscription to one of two paid plans: $49 or $249.
To handle the payment flow, I am utilizing React’s payment elements to display the payment form and invoice. I rely on the payment_succeeded event in webhooks to trigger the subscription upgrade process. This setup works fine when a user completes the payment.
However, I've encountered an issue when a user attempts to upgrade their plan. The payment form appears with an option to cancel the upgrade. If the user decides to cancel, the current invoice is marked as void, which seems to be functioning as expected.
The problem arises when the user tries to upgrade again after canceling. In this case, the system automatically updates the subscription plan without prompting the user to make a new payment, due to "unused time" from the previous subscription. I have attached the invoice for reference. This is not the behavior I intend to have.
My question is: Is this behavior expected based on the current flow I’ve implemented? Or do I need to adjust the process in some way to ensure that the subscription plan is only upgraded after a successful payment, regardless of any unused time left on the previous plan?
looks like yuo only want to update the subscription when the payment is successful? if so, you should use pending updated as mentioned above. this way there are no invoices to void.
Okay let me try that
what doesn't work?
When i try to upgrade it still use the credits from previous invoice and updates subscription without payment
are you talking about the Customer Balance? and you want to ignore the Customer Balance? if so, you can set it to 0 if needed.
How ? if I void an invoice that invoice amount is used as credit for next upgrade and that way user is not charged, how to fix that ? is what I am asking
fix what? if the user paid $10, and then you void the invoice, then it's normal that the user gets back $10 as credit.
if you don't want this, then you can set the user's credit back to 0. https://docs.stripe.com/api/customers/update?api-version=2024-09-30.acacia#update_customer-balance
can you be more precise? what is not working exactly?
can you share a specific request ID or something that doesn't work?
what's the issue with this screenshot?
In my ui with the payment element form I have a cancel button (on press - void invoice, reset customer balance -0), when I try to upgrade to a plan with lesser price it uses the credit from voided invoice and considers it payment for the new price
I don't understand why/how you have a "cancel button". can you share more details on this?
I dont want that to happen, my entire flow is working except for the part when user clicks on upgrade but decides to not go through with the payment
Watch this, it should help
so you didn't follow the suggestion we gave you at the beginnng to use pending updates?
I really don't understand your payment flow. why do you show the payment element when they click upgrade?
When a user signs up to my app, they get a customer and subscription (0$) in stripe without payment method attached, when the user logs in they can upgrade to plans that require payment, I use the update method from this controller to create an invoice and send the payment intent and client secret to front based on if user has a payment method attached or not, during that in the payement element form the user can decide to not pay and just cancel - this is where all hell breaks loose, when they try again the price from previous is saved as credit and used as payment for the new price, which is what I dont want, I am voiding the invoice and resetting the customer balance on 'cancel' click still it did not work
I am using this -
const updatePayload: Stripe.SubscriptionUpdateParams = {
items: [{ id: itemId, price: newPriceId }],
payment_behavior: "pending_if_incomplete",
proration_behavior: "always_invoice",
};
Because after a sign up I need use to add payment method if theres none, i show the payment element or else I just use default payment method to update the price and charge the customer
Did you try the pending update fucntionality as noted? This does exactly as you describes – rolls back the update so the sub is reverted to the previous state if the payment is not made within a timeframe
Yes but it does not work if the customer has no payment methods attached
What do you mean by 'doesn't work'?
which is the case when the user tries to update the subscription for the first time, since they have a 0$ subscription initially and I dont collect the payment method
This is what I get - {
"message": "Failed to update subscription",
"error": "This customer has no attached payment source or default payment method. Please consider adding a default payment method. For more information, visit https://stripe.com/docs/billing/subscriptions/payment-methods-setting#payment-method-priority."
}
Yeah then you're likely going to have to adapt your flow a bit to collect payment details first before applying the update
I cant make that change any alternative ?
If i suggest that now, most likely I lose the project
Other than manually rolling back the update? Not really, no
Will this flow work-
I dont create a subscription when the user signs up just a customer and then, when user tries to upgrade I check if they have a subscription with a payment method if yes I just update that if not then I create a new subscription with payment method and then the next time the upgrade checks for the subscription it finds the payment method attached and I use -
const updatePayload: Stripe.SubscriptionUpdateParams = {
items: [{ id: itemId, price: newPriceId }],
payment_behavior: "pending_if_incomplete",
proration_behavior: "always_invoice",
};
to update the subscription, but meanwhile before making the pay the uer decides to cancel the payment and I void the invoice but then since I am using pending_if_incomplete it wont try to use credit from previously voided invoice and just make a new payment adn update the subscription ?
Sounds like it could work, yes
Can you please point out if anything could go wrong here? I need to be very sure to suggest this change, I cannot do trial and error anymore, or do you know of any examples or templates on github that implement recurring subsriptions using payment element that I can use for reference ?
The only part not working in my current flow is when I void the invoice, stripe still uses the price of that invoice to credit next invoice without capturing any real payments
Nothing for your specific use case, no. You should just use test clocks to test out the variou scenarios you described to ensure it works – you can 'cheat' time with them
Not sure what you're describing here
In the payment element form if user decides not to make payment, I allow them to cancel which just voids their invoice, but based on my current implementation I am collecting payment method on an existing subscription using this -
proration_behavior: "none",
payment_behavior: "default_incomplete",
expand: ["latest_invoice.payment_intent"],
metadata: {
prev_price_id: currentSubscriptionItem.price.id,
current_price_id: newPriceId,
},
So with this config, if I void the invoice (i also reset customer balance to 0 alongside) the amount of that invoice is stored as credit, and used in the next invoice even though in this scenario no actual payment has been made. So if I switch to this flow -
I dont create a subscription when the user signs up just a customer and then, when user tries to upgrade I check if they have a subscription with a payment method if yes I just update that if not then I create a new subscription with payment method and then the next time the upgrade checks for the subscription it finds the payment method attached and I use -
const updatePayload: Stripe.SubscriptionUpdateParams = {
items: [{ id: itemId, price: newPriceId }],
payment_behavior: "pending_if_incomplete",
proration_behavior: "always_invoice",
};
to update the subscription, but meanwhile before making the pay the uer decides to cancel the payment and I void the invoice but then since I am using
pending_if_incomplete
it wont try to use credit from previously voided invoice and just make a new payment adn update the subscription ?
Will stripe not use the amount from void invoice as credit for next invoice ?
Watch this
This flow tries to update existing subscription (0$) without payment method
It'd be easier if you just gave us a sub_xxx ID
I don't see where the customer balance is credited from the voiding of an invoice?
hey @tender quail ?
I replied above?
My bad didnt see that, but can you look at the screenshot from the sub id?
Look at the applied balance
See the unused time ? thats the negative balance thats causing the issues
The negative value there is because of prorations, not customer credit. We applied credit for unused time when you created the invoice (in_1RPIcrQ5lokX8cUN52l3DTFS) in this sub update: https://dashboard.stripe.com/test/logs/req_cTcS33GvwhJFgh
Sign in to the Stripe Dashboard to manage business payments and operations in your account. Manage payments and refunds, respond to disputes and more.
but my entire codebase has proration_behavior: "none", still it would prorate also is it because payment_behavior: "default_incomplete" even a voided invoice is considered as paid and hence the proration ?
How do you completely remove that ? no credit carry over and no proration ?
Checking on this as I'm not sure what is expected or not
Okay, let me know please
Also would this flow, I mentioned earlier work? -
I dont create a subscription when the user signs up just a customer and then, when user tries to upgrade I check if they have a subscription with a payment method if yes I just update that if not then I create a new subscription with payment method and then the next time the upgrade checks for the subscription it finds the payment method attached and I use -
const updatePayload: Stripe.SubscriptionUpdateParams = {
items: [{ id: itemId, price: newPriceId }],
payment_behavior: "pending_if_incomplete",
proration_behavior: "always_invoice",
};
to update the subscription, but meanwhile before making the pay the uer decides to cancel the payment and I void the invoice but then since I am using
pending_if_incomplete
it wont try to use credit from previously voided invoice and just make a new payment adn update the subscription ?
Thanks
I answered that didn't I? You should just try it – use a test clock so you can advance the time as required
We can't answer every specific use case and all the nuances that might be problematic for you, that's where your testing comes in
Fundamentally your approach should work though
OK, apparently this is expected and you'd need to reset the billing anchor in your update call: billing_cycle_anchor: 'now' which should prevent any prorations for unpaid invoices
Okay will try it. Thanks alot