Subscription#trial_end=now leaves trial open if card declined

420 views
Skip to first unread message

Ryan McGeary

unread,
Aug 22, 2014, 4:42:00 PM8/22/14
to api-d...@lists.stripe.com
Prerequisite:

1. Take a customer with a valid and tokenized card and build a subscription (with a trial period) for that customer.
2. Add invoice items to the subscription
3. Update the subscription such that trial_end=now
4. The customer's card is declined.

Expected behavior:

1. The subscription trial_end date end now.
2. An invoice is created for that customer
3. An invoice.payment_failed event fires
4. Payment retries are attempted according to the account's schedule

Actual (current) behavior:

0. The response for updating the subscription (trial_end=now) is a 402 error stating the card was declined.
1. The subscription's trial_end date is extended beyond the current trial period to the next potentially billed interval (unexpected)
2. No invoices are created.
3. No invoice.payment_failed notifications are sent (no invoice afterall)
4. No payment retries

Can someone please justify this behavior? Better yet, did we miss something in the API docs about this?

Thanks,
Ryan


Jim Danz

unread,
Aug 22, 2014, 9:35:08 PM8/22/14
to api-d...@lists.stripe.com
Hi Ryan,

When a requested subscription modification requires a payment, Stripe
synchronously collects billables and attempts payment. If that
payment succeeds, the subscription update is committed, and an invoice
is saved to reflect the billing that occurred. If that payment fails,
you receive a 402, the subscription update is never committed, and no
invoice is saved (no invoice.payment_failed event).

The motivation here stems from the customer creation case: If an
attempt to create a customer (with a subscription) fails, the customer
creation itself is never committed, no invoice is saved, etc. There
is a just a 402 response, with no ghost objects (other than the failed
charge).

The other piece of motivation stems from the upgrade case --
attempting to move the customer to a paid plan, or off a trial, is an
"upgrade" so to speak, and the semantics that we provide are that if
the required payment failed, the upgrade never goes into affect.

I am surprised to see "1. The subscription's trial_end date is
extended beyond the current trial period to the next potentially
billed interval (unexpected)". This is not consistent with the
expected behavior on our end. I just did a manual test on my end of
(I believe) the steps that you provided, and wasn't able to reproduce
the behavior: https://gist.github.com/jimdanz/cbfff1d825f7f2e58296

I'm hunting up some edits to the docs to make this more clear, but
also happy to answer any followup questions!

Cheers,
Jim
> --
> You received this message because you are subscribed to the Google Groups
> "Stripe API Discussion" group.
> To post to this group, send email to api-d...@lists.stripe.com.
> Visit this group at
> http://groups.google.com/a/lists.stripe.com/group/api-discuss/.
>
> To unsubscribe from this group and stop receiving emails from it, send an
> email to api-discuss...@lists.stripe.com.

Ryan McGeary

unread,
Aug 23, 2014, 10:01:16 AM8/23/14
to api-d...@lists.stripe.com
Hi Jim,

Thanks for the detailed explanation. I better understand the reasoning now and I understand where Stripe was trying to be consistent, but I have a few comments about how this behavior is actually inconsistent with everything else related to a subscription. I realize this behavior is probably unlikely to change as it would affect all your existing customers, but I still want to document the inconsistency.

But first, you're right about not being able to reproduce the trial_end=now causing a trial extension on a failed payment. I misinterpreted what I was seeing in our account. We had manually extended the trial on this subscription after the failure, and I thought it came from the trial_end=now call. My mistake there. Sorry for the confusion.

I understand that it's desirable to avoid creation of all objects if everything is put into one single API call (customer + card + subscription are all created with the facilitator params while creating a new customer). However, when you approach it from a different angle (customer + card created successfully, then a subscription with trial is created successfully, then the card is declined on the first charge attempt), I believe this behavior should be different. Instead of mimicking the behavior of a combined customer creation, it should instead mimic what would happen on a failed 2nd invoice of a subscription if the first charge on the subscription was successful.

In fact, this is related to one of my major gripes with how Stripe subscriptions work. The first invoice on a subscription is inconsistent with the behavior of all subsequent invoices. The amount of logic that we have to add to our Stripe Connect application to workaround this inconsistent behavior is a bit of a burden. I could go into more detail about what we're specifically trying to do, but it might be more productive if we take that offline. The Stripe Connect application we're building is somewhat unique compared to others listed in Stripe's application integration doc.

Some examples of subscription invoice inconsistencies (all of these assume the subscription started with a trial period, which actually generates a 0th $0 invoice):
1) The first invoice on a subscription is charged immediately before the invoice.created event. Subsequent invoices are charged a few hours after the invoice.created event. I would prefer the first invoice to also wait a few hours before charging, allowing invoice items to be added to the invoice. Instead we must add invoice items to the subscription and then monkey with trial_end dates to get our desired behavior on the first invoice without worrying about race conditions on customers with multiple subscriptions.
2) A failed charge on the first subscription charge creates no invoice, and does not start any retry attempts. A failed charge on subsequent subscription charges builds an invoice, marks it as failed, and starts the retry schedule. I would prefer that a failure on the first subscription charge build a failed invoice just like failed payments on subsequent charges.

Does my explanation make sense? I'd be happy to jump on a quick phone call sometime to discuss in more detail if that would help.

-Ryan

Vladimir Andrijevik

unread,
Aug 26, 2014, 6:33:12 AM8/26/14
to api-d...@lists.stripe.com
Hi Ryan,

I had the same complaint a few months ago and agree with your points about the inconsistency here.

While this is certainly not ideal, the way I ended up getting behavior closer to that of subsequent invoices was to set trial_end to 1.minute.from_now instead of "now" in your example. The 1 minute window is plenty to account for differences between Stripe’s clocks and your server clocks, yet close enough to now that it doesn’t make a difference most of the time (with the exception of day boundaries). That makes it an async charge, so it will create invoices and obey the dunning schedule even when payments fail, reducing code you need to maintain on your end.

Hope that helps, and +1 to the desired behaviour you mentioned!

Cheers,
Vlad

Ryan McGeary

unread,
Aug 26, 2014, 10:50:25 AM8/26/14
to api-d...@lists.stripe.com
Hi Vlad,

Thanks for chiming in. Just to clarify, setting a trial_end date for just a few minutes in the future does actually trigger a new invoice, but setting trail_end=now doesn't?  Let's log this as a third point of inconsistency with subscription invoices. It sounds like the first invoice on a subscription is not only inconsistent with future invoices, but it's also inconsistent with itself.

I'll see if we can run with the 1 minute in future trial_end trick. That might help.

Thanks,
-Ryan

To unsubscribe from this group and stop receiving emails from it, send an email to api-discuss+unsubscribe@lists.stripe.com.

Vladimir Andrijevik

unread,
Aug 26, 2014, 12:46:12 PM8/26/14
to api-d...@lists.stripe.com
Hi Ryan,

The way I find it helpful to think about this is not in terms of first invoices, but whether the create & update API calls for subscriptions require an immediate charge or not. For example:

- If you create a new subscription with a trial_end in the future, that API call does not require an immediate charge, so it will succeed regardless of whether the customer’s default card is valid or not. Afterwards, at trial_end time, an invoice will be created and a charge for it will be attempted.
- If you create a new subscription without a trial_end parameter, that API call requires an immediate charge (for the subscription period which begins that very moment), so the API result depends on the immediate charge: if it succeeds, the subscription created and all is well; if it fails, the subscription is not created.

The above applies when updating an existing subscription also, but the conditions for whether an immediate charge is a bit different: if you set prorate to false, an immediate charge never happens regardless of plan/quantity changes, so the call succeeds whether the customer’s card is valid or not; if, on the other hand, you change a subscrpition from a monthly to a yearly plan, there is an immediate charge so the result depends on whether the customer’s card is valid or not.

There are other cases when there are no immediate charges (updating metadata only, as an example), but I hope this helps clarify things a bit. If I’ve misrepresented the logic above, I’m sure Jim will jump in and correct me here :)

Cheers,
Vlad
Reply all
Reply to author
Forward
0 new messages