#braydensterrett
1 messages · Page 1 of 1 (latest)
What's the question
So I've worked with some Stripe support resources before on how to understand when a subscription is renewing due to the billing_reason being subscription_cycle.
I'm looking at an invoice.paid event here: https://dashboard.stripe.com/events/evt_1O7O2tALivADE4JrgvK8zfmn
Sign in to the Stripe Dashboard to manage business payments and operations in your account. Manage payments and refunds, respond to disputes and more.
What's confusing to me is that the subscription billing_cycle_anchor is on Oct 31, 2022 (which is expected because this is when the Subscription started)
But the invoice.lines.data.first.period.start is Oct 30, 2023 instead of Oct 31, 2023. Can you explain to me why this is so?
We're trying to use this value (at the recommendation of Stripe Support) to determine if this invoice.paid event is the subscription renewing itself or just a one-off invoice. Stripe support has recommended that this value be compared with our subscription start date + 1.year in order to guarantee this.
The reason this is important is that we also have subscriptions that are split into two 6 month portions so we can't just key off of the billing_reason being subscription_cycle. We have to verify this condition AND verify the invoice period start to confirm that this is a year renewal. We use this to make some updates on our side so the customer can see that their subscription successfully renewed
FWIW, the dates, subscription billing cycle anchor and invoice period start, are exactly 1 full year apart on 95% of the subscription renewals that we see. There's this very small portion of subscriptions that are not though, and I cannot for the life of me figure out why.
I've got a few more example events if that's helpful!
Sorry for the delay
Reading now
I think this is because you're using a subscription schedule
What part about that would cause this?
Because we do use a subscription schedule, but mainly because our subscription price changes for most subscriptions on renewal and that was the easiest way to get that to happen automatically in Stripe
I can post our subscription schedule update code if you want to see that.
Sorry this is a bit complex
Juggling many threads
Haven't had a proper chance to dive deep
Just to also point out, we use subscription schedules for ALL of our subscriptions and only like 5% experience this issue..
So I'm not ruling that out as being the cause.. it just seems unlikely that so many would work on the schedule and so few would experience issue. If it were caused by the schedule I would expect all of them to have an issue like this..
Ok I recommend writing in to support about this
It's difficult to deep dive into something like this when juggling many threads here in discord
Support should be able to give you a better clue
Find help and support for Stripe. Our support site provides answers on all types of situations, including account information, charges and refunds, and subscriptions information. Get your questions answered and find international support for Stripe.
But hold on actually
If you don't mind waiting I can spend some more time
Great, thank you!
HI 👋
I'm jumping in as @inland robin needs to go.
Looking at the the subscription schedule creation request: https://dashboard.stripe.com/logs/req_QQ0JJgYBlGD5Jl, the first phase had a natural end date of 10/31.
Your update API call: https://dashboard.stripe.com/logs/req_oXeDUcrfXNIhPk modified the end date to 10/30 which generated a small propration to account for the 1 day discrepancy.
Was the end date modified because of the start date that was provided? Because this start date just comes from the current schedule before it's released
You are using a 6mo price and so that re-set the end date as 6 months out from the start
Ah, yeah I see it.
So what's the difference between doing this for a 6 month price and a 1 year price?
I'm not sure I follow
What we're trying to do with the API call that you linked there is to update the renewal price without affecting the current schedule phase
When we do this for subs with a 1 year price, the end date is still 1 year out from the start after this schedule update is made
Why is the 6 month price causing this to happen?
Is there a difference in length between two 6-month price phases and a 1 year phase?
No difference, the issue here is the end date explicitly set did not match the implicit end date created by the update. Date/time matching can be tricky so it can be easier to get an exact match for 1 year phases since the day/month combo remains the same
Interesting, okay I think that makes sense conceptually.. Any idea how we can mitigate this for 6 month prices?
Can we like explicitly set an end date? Would that help us?
I think you would still run into problems there with the end date potentially not aligning with the 6 month duration.
Hmmm is there any other way you can think of that we can identify this as a year renewal of a subscription? We do it this way because Stripe support recommended it in this very channel
Because we need to be able to weed out any 6 month subscription_cyle invoice.paid events, we use this to determine that an entire year has gone by
Well one approach you could use, looking at the /update API call, would be to just copy first phase data from the Subscription schedule object itself into the new array you pass. That would ensure it uses the exact same start/end date.
I think this is what we do.. the start_date in that current phase is pulled from the current schedule
We create a schedule from the subscription itself:
sched = Stripe::SubscriptionSchedule.create(from_subscription: subscription.id)
Then we pass that as the start_date in the first phase of the subscription schedule update:
start_date: sched.phases[0].start_date
Is that what you're talking about?
I was thinking that, when you want to create the update call, you could just copy the entire sched.phases array, since it only has the first phase in it at that point. Then you add your second phase to it and pass the whole thing back in the /update call.
Oh, interesting.. if we were in the first 6 months of the year long subscription, I guess this would still work?
Or I guess the iterations would just be 2 inside of that phase on the current schedule?
I'm not sure I get what you mean by that. I mean that, by passing the exact data from the phase that was created during the conversion to schedule, you would not create any prorations
and any changes would only apply once that first phase was completed
So it would look something like this:
Stripe::SubscriptionSchedule.release(subscription.schedule) if subscription.schedule
sched = Stripe::SubscriptionSchedule.create(from_subscription: subscription.id)
new_phases = sched.phases.push({
iterations: 2,
items: [{
price_data: {
unit_amount: (renewal.invoice_amount * 100).round.to_i,
currency: "usd",
product: ENV.fetch("STRIPE_PRODUCT_ID"),
recurring: { interval: "month", interval_count: 6}
}
}]
}
Stripe::SubscriptionSchedule.update(
sched.id,
{
end_behavior: "release",
phases: new_phases,
proration_behavior: "none"
}
)
Exactly
Then you are guaranteed the update does not change anything about the current phase
Well that's a helluva lot more simple than what we've been instructed to do in the past 🤣
I'll do some testing around it and see if it works!
Thanks a bunch.
Sure thing! happy to help 🙂
Hey, so I just tried to do that and it appears that pushing in that second phase doesn't really work inside of the update call for the sub schedule..
Do you have the API request ID I can take a look at?
When I access the phases from the current schedule, it's an object not just a hash
Maybe, one sec
Sign in to the Stripe Dashboard to manage business payments and operations in your account. Manage payments and refunds, respond to disputes and more.
Yeah, this one
I think the problem is the second phase that I push in there is just a hash and not an object like the first index of that phases array?
Do I need to like convert that to JSON or something?
Okay yeah the initial phase is being passed as a string
This is what I see in the API request body for the initial phase
...
phases: {
0: "{\n \"add_invoice_items\": [\n\n ],\n \"application_fee_percent\": null,\n \"automatic_tax\": {\n \"enabled\": false\n },\n \"billing_cycle_anchor\": null,\n \"billing_thresholds\": null,\n \"collection_method\": null,\n \"coupon\": null,\n \"currency\": \"usd\",\n \"default_payment_method\": null,\n \"default_tax_rates\": [\n\n ],\n \"description\": null,\n \"end_date\": 1699123375,\n \"invoice_settings\": null,\n \"metadata\": {\n },\n \"on_behalf_of\": null,\n \"plans\": [\n {\n \"billing_thresholds\": null,\n \"metadata\": {\n },\n \"plan\": \"price_1L7prRALivADE4Jr1IpwxHic\",\n \"price\": \"price_1L7prRALivADE4Jr1IpwxHic\",\n \"quantity\": 1,\n \"tax_rates\": [\n\n ]\n }\n ],\n \"prorate\": true,\n \"proration_behavior\": \"create_prorations\",\n \"start_date\": 1683225775,\n \"tax_percent\": null,\n \"transfer_data\": null,\n \"trial_end\": null\n}",
...
Is there a method I can call to get that in a different format?
Just a simple like to_h or something?
How is it expected?
Sorry let me show you what the whole payload looks like
{
end_behavior: "release",
proration_behavior: "none",
phases: {
0: "{\n \"add_invoice_items\": [\n\n ],\n \"application_fee_percent\": null,\n \"automatic_tax\": {\n \"enabled\": false\n },\n \"billing_cycle_anchor\": null,\n \"billing_thresholds\": null,\n \"collection_method\": null,\n \"coupon\": null,\n \"currency\": \"usd\",\n \"default_payment_method\": null,\n \"default_tax_rates\": [\n\n ],\n \"description\": null,\n \"end_date\": 1699123375,\n \"invoice_settings\": null,\n \"metadata\": {\n },\n \"on_behalf_of\": null,\n \"plans\": [\n {\n \"billing_thresholds\": null,\n \"metadata\": {\n },\n \"plan\": \"price_1L7prRALivADE4Jr1IpwxHic\",\n \"price\": \"price_1L7prRALivADE4Jr1IpwxHic\",\n \"quantity\": 1,\n \"tax_rates\": [\n\n ]\n }\n ],\n \"prorate\": true,\n \"proration_behavior\": \"create_prorations\",\n \"start_date\": 1683225775,\n \"tax_percent\": null,\n \"transfer_data\": null,\n \"trial_end\": null\n}",
1: {
items: {
0: {
price_data: {
recurring: {
interval: "month",
interval_count: "6",
},
unit_amount: "126400",
product: "prod_HgPib8F8aI2Fj9",
currency: "usd",
},
},
},
iterations: "2",
},
},
}
You see how the second phase is structured? that is what the API expects.
I would try copying the first phase to it's own variable and try some different methods of converting that first phase to an object
Great!
Is there a way I can preview the invoice that will happen on Nov. 4th here?
Sign in to the Stripe Dashboard to manage business payments and operations in your account. Manage payments and refunds, respond to disputes and more.
I updated this schedule in the new way suggested and just want to see if I can see the start date on the invoice coming up tomorrow
yes, you can pass the schedule ID in to the Upcoming Invoice API request; https://stripe.com/docs/api/invoices/upcoming
Beauty, thanks!