#braydensterrett

1 messages · Page 1 of 1 (latest)

lavish fractalBOT
inland robin
#

What's the question

ripe yacht
#

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.

#

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!

inland robin
#

Sorry for the delay

#

Reading now

#

I think this is because you're using a subscription schedule

ripe yacht
#

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.

inland robin
#

Sorry this is a bit complex

#

Juggling many threads

#

Haven't had a proper chance to dive deep

ripe yacht
#

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

inland robin
#

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

ripe yacht
#

Hmm how do I do that?

#

I'm usually directed here for technical support

inland robin
#
#

But hold on actually

#

If you don't mind waiting I can spend some more time

ripe yacht
#

Sure, no problem.

#

I've got all day lol

inland robin
#

Asking a colleague to take a look

#

Will get back to you

lavish fractalBOT
ripe yacht
#

Great, thank you!

spare kite
ripe yacht
#

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

spare kite
#

You are using a 6mo price and so that re-set the end date as 6 months out from the start

ripe yacht
#

Ah, yeah I see it.

#

So what's the difference between doing this for a 6 month price and a 1 year price?

spare kite
#

I'm not sure I follow

ripe yacht
#

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?

spare kite
#

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

ripe yacht
#

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?

spare kite
#

I think you would still run into problems there with the end date potentially not aligning with the 6 month duration.

ripe yacht
#

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

spare kite
#

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.

ripe yacht
#

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?

spare kite
#

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.

ripe yacht
#

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?

spare kite
#

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

ripe yacht
#

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"
  }
)
spare kite
#

Exactly

#

Then you are guaranteed the update does not change anything about the current phase

ripe yacht
#

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.

spare kite
#

Sure thing! happy to help 🙂

ripe yacht
#

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

spare kite
#

Do you have the API request ID I can take a look at?

ripe yacht
#

When I access the phases from the current schedule, it's an object not just a hash

#

Maybe, one sec

#

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?

spare kite
#

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}",
...
ripe yacht
#

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?

spare kite
#

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

ripe yacht
#

Yeah, got it. Working on that

#

Got it figured out.

spare kite
#

Great!

ripe yacht
#

Is there a way I can preview the invoice that will happen on Nov. 4th here?

#

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

spare kite
ripe yacht
#

Beauty, thanks!