Recurly subscriptions can be in

"active", "canceled", "expired", "future", "in_trial", "live", or "past_due". A subscription will belong to more than one state. For example, a subscription may be active and in a trial.

. The idea of an receiving back an array of states or at least an additional past_due subscription uuid(s) when calling $subscriptions = Recurly_SubscriptionList::getForAccount('1'); may be something we could bring up to Recurly and they would support. The tricky part is that while a subscription may be in a past_due state, while it's in the Dunning process, there isn't much you can do as far as I can see other than act on incoming payment notifications to update state. (which is what they recommend after a discussion I had with them) Since we really need to show a past due state on the user-facing subscription page if that is the case, I'm proposing to try and achieve that, which means either keeping track locally of subscription state via push notifications or finding a way to do this using their SDK.

Comments

quicksketch’s picture

or finding a way to do this using their SDK.

I haven't been able to find a way to do this via the SDK. Oddly when getting a list of subscriptions like this:

  $subscription_list = Recurly_SubscriptionList::getForAccount($account->account_code, array('per_page' => $per_page));

All the subscriptions all just have state == 'active', no mention of the fact that they're past due at all. Seems like a pretty big oversight on the part of the API. :(

Any public discussions that you've seen about this topic?

quicksketch’s picture

Ohhh, I get it now. In their API page on subscription the "past_due" state is only a *query* option, it's not actually in the returned $subscription object at all. http://docs.recurly.com/api/subscriptions

The subscription object will always be in only one of the following: "active", "canceled", "future", "expired", "modified". However you may query for specifically "past_due" subscriptions if desired, though these subscriptions will still have state "active" when you examine the subscription object.

So one way we could handle this is by querying for "past_due" subscriptions or optionally for a "past_due" account, since there's only one billing address per account they're really equivalent for the most part.

Finally we could also query for past_due invoices, which may be the most helpful indicator since we could actually link to the invoices that are currently unpaid. Invoices (unlike accounts or subscriptions) actually reflect the "past_due" state in the invoice object.

quicksketch’s picture

Even though it's not documented that you can pass "state" into the list of parameters, both of these queries work:

  // Get past due subscriptions.
  $subscriptions = Recurly_SubscriptionList::getForAccount('1', array('state' => 'past_due'));

  // Get past due invoices.
  $invoices = Recurly_InvoiceList::getForAccount('1', array('state' => 'past_due'));

Likewise the PHP SDK also includes a method for getPastDue invoices, which also can take an account code, so this is equivalent to the second query above, though it makes more sense to me personally (though internally it's doing the exact same GET query):

  // Get past due invoices.
  $invoices = Recurly_InvoiceList::getPastDue(array('account_code' => '1'));

So looks like we've got lots of options for discovering closed/trial/past_due subscriptions, though it will require separate requests.

blasthaus’s picture

I like the invoice approach as well, since this is ultimately what gets sent to the user via email from Recurly, then you just have a link to the failing invoice(s) and a call to update the billing.

The separate requests are a bit of a drag, I wonder if we could suggest to them, like I was mentioning, when querying an account or subscription that is actually past_due (but returning as active), to include some additional/helpful information. They are doing this with a pending subscription for example. It's only there when the subscription plan is going to change at renewal. Why not include a failing invoices array or something to alert us that this is a past_due account or subscription? Or maybe just at least an empty past_due tag so we don't have to call each and every account/sub twice just to get the most basic info.

Great that you are digging into their SDK, nice work!

quicksketch’s picture

The separate requests are a bit of a drag, I wonder if we could suggest to them, like I was mentioning, when querying an account or subscription that is actually past_due (but returning as active), to include some additional/helpful information.

Yes I think we should definitely pursue asking Recurly to improve the API in this regard. In the mean time, we might make a wrapper function (similar to recurly_get_subscriptions()) that retrieves all the states of all subscriptions for a Recurly account. I think that would come down to the following requests:

  $past_due = Recurly_SubscriptionList::getForAccount('1', array('state' => 'past_due'));
  $in_trial = Recurly_SubscriptionList::getForAccount('1', array('state' => 'in_trial'));
  $canceled = Recurly_SubscriptionList::getForAccount('1', array('state' => 'canceled'));
  // Getting live subscriptions seems like a waste. We already know if it's "active".
  $live = Recurly_SubscriptionList::getForAccount('1', array('state' => 'live'));

We can then compare the returned lists against the full list of accounts to determine the states of all of them. A few remaining states are already given to us directly in the normal subscription object. These are: "active", "expired", and "future". The SDK also includes a "non_renewing" state that is not mentioned in the API, it seems as though it's the same as "canceled" for auto-renewing subscriptions, but perhaps it's also set for any subscription that is a fixed time period. It doesn't seem like something worthwhile to retrieve IMO, since I think sites are likely to either use all auto-renewing or fixed-length plans, rather than a mix of both.

So this means that when we show the main subscriptions listing page, we can get all the information we need with 3 additional API requests. We're currently only doing 2 (one for the account and one for the subscription list), so this is a bit of unfortunate overhead just to get subscription states.

quicksketch’s picture

Actually if the "expires_at" property exists as advertised (I mentioned it in #1632284-3: How to handle subscription total_billing_cycles and first_renewal_date subscription params), we don't need to check for canceled or non_renewing. Actually continuing that thought process, you can use "trial_starts_at" and "trial_ends_at" to determine if the user is in a trial state. So in fact it seems that the *only* state you can't get derive directly from the subscription object is "past_due".

blasthaus’s picture

So we're talking about 4 api request now. account, subscriptions for account, in_trial subscription list and past due subscription list for account?

One thing to keep in mind is subscriptions aren't directly tied to the dunning status of an account. Renewals generate an invoice which in turn will be marked as past_due if failing payments. I think keeping an eye on the invoices is important in this respect as its what is triggering the past_due state. So maybe instead use invoices for an account and link it back to the subscription? Ultimately going down this road you will at best wind up with a past_due state on the account and not the failing subscription, since you don't have the subscription uuid linked to anything locally. This is what frustrated me.

I am getting the in_trial from the local product and doing a date calculation to determine if it's still in trial. (keep in mind I have the date_created stored locally too). This works for me but I'm open to change. I see that your goal is to get everything from Recurly for lower PCI exposure on these user-facing pages, so it makes sense.

quicksketch’s picture

In the PHP SDK, "expires_at" does not seems to exist. :(

However we have other information (again not mentioned in the API) for total and remaining billing cycles:

        'activated_at' => 
        DateTime::__set_state(array(
           'date' => '2012-06-13 23:15:49',
           'timezone_type' => 2,
           'timezone' => 'Z',
        )),
        'total_billing_cycles' => 12,
        'remaining_billing_cycles' => 12,
        'current_period_started_at' => 
        DateTime::__set_state(array(
           'date' => '2012-06-13 23:15:49',
           'timezone_type' => 2,
           'timezone' => 'Z',
        )),
        'current_period_ends_at' => 
        DateTime::__set_state(array(
           'date' => '2012-06-16 23:15:49',
           'timezone_type' => 2,
           'timezone' => 'Z',
        )),
        'trial_started_at' => 
        DateTime::__set_state(array(
           'date' => '2012-06-13 23:15:49',
           'timezone_type' => 2,
           'timezone' => 'Z',
        )),
        'trial_ends_at' => 
        DateTime::__set_state(array(
           'date' => '2012-06-16 23:15:49',
           'timezone_type' => 2,
           'timezone' => 'Z',
        )),

So I would think that "remaining_billing_cycles" would be exactly what you would be interested in as far as warning users whose subscription is nearing its end (when remaining_billing_cycles is 0, you should start the pestering).

blasthaus’s picture

good find once again! thats the ticket. I somehow recall that being there now and I have to keep reminding myself not to look first at their docs but just at the return data. %) I found out for example the 'modified' state doesn't even exist anymore.

quicksketch’s picture

Status: Active » Fixed
StatusFileSize
new43.07 KB
new79.2 KB
new16.58 KB

This patch gets the basics handled for this problem. It adds a class to each invoice row in the invoice list and it shows a message on each subscription to reflect subscriptions that are past due or in a trial. Bunch of minor fixes included also.

blasthaus’s picture

cool. man that was fast. i thought it may be wise to post this page Recurly Changelog which gives us some insight into the changes they are making.

blasthaus’s picture

Looks like this issue is fixed but just to add a note here to test

Actually continuing that thought process, you can use "trial_starts_at" and "trial_ends_at" to determine if the user is in a trial state.

for a subscription which is not in a trial. I seem to recall that these properties may linger, but I could be wrong!

quicksketch’s picture

for a subscription which is not in a trial. I seem to recall that these properties may linger, but I could be wrong!

Yes that's true, they stick around. I calculated if the subscription is in-trial by comparing the current date against the start and end dates of the trial, so when the trial ends the status properly reflects that.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.