Becca Royal-Gordon
Becca Royal-Gordon

Reputation: 17881

Resuming a subscription with Stripe (and charging for previously unpaid service)

I have a web service which publishes and hosts certain information for my users. Each user's editing interface is separate from the information they've published. I'm billing for it using Stripe subscriptions, but I'm having trouble coming up with a workable way of handling unpaid subscriptions. Here's the logic I want to implement:

  1. Upon the first failed billing attempt, my app should lock down the user's editing interface; instead, it should present them with a page with options for resolving their payment problem. However, information they've published will still be published, so a late payment doesn't interrupt things for their visitors.
  2. Upon the last failed billing attempt, 15 days later, my app should remove information they've published as well. At this point, the user's editing interface is replaced with one that allows them to either re-activate their account with a new credit card, or completely delete the account.
  3. If the user chooses to re-activate the account, they should not get a trial period
  4. If the user re-activates, they should also have to pay for the 15 days they previously skipped out on, either by having their first billing period shortened by 15 days or by having a charge equal to 15 days' service added to their first bill.

#1 and #2 are very easy to implement if I watch for the right webhooks—just redirect requests for a delinquent customer, or a customer with a deleted or unpaid subscription, to a "fix this problem" page. #3 is pretty easy too—just create the subscription with trial_end set to now.

It's #4 that's tricky. How do I determine exactly how long the user's last invoice went unpaid before their subscription was canceled? How do I shorten the first billing period of the new subscription? Alternately, how do I pro-rate the customer's last invoice total to represent the portion that I actually provided to them, so I can add that amount to the new invoice? (I know I can create an Invoice Item to charge for the amount before I start the new subscription, so at least that's a possibility.)

Finally, does Stripe's "Mark Subscription Unpaid" option help with any of this? It looks like it keeps creating invoices but doesn't try to charge for them; I don't think that's what I want here.

Upvotes: 2

Views: 4207

Answers (1)

koopajah
koopajah

Reputation: 25642

You can definitely do all of that with Stripe's subscriptions. As you mentioned, the first two questions should be solved with webhooks. You would listen for invoice.payment_failedon your end to detect when a payment fails and immediately block you UI asking your customer to update their card details.

To update their card details you can use Stripe Checkout and just not set the data-amount parameter so that it doesn't display a price to your customer. Here is an example for this:

<form action="/charge" method="POST">
  <script
    src="https://checkout.stripe.com/checkout.js" class="stripe-button"
    data-key="pk_test_XXXX"
    data-image="/square-image.png"
    data-name="Demo Site"
    data-description="Update Card Details"
    data-panel-label="Update Card Details"
    data-label="Update Card Details">
  </script>
</form>

Each time your customer tries to update his card, you would use the Update Customer API and pass the new card token in the card parameter. This will delete the default card on the customer and replace it with the new one. It will also try and pay the latest invoice of each subscription in the "Past Due" state. In your case it means that the latest invoice still in retry mode will be attempted to be paid. This does not count as a retry and can be seen as independent. If it succeeds, you just reenable the UI otherwise you continue asking the customer to update his card details.

When you reach the fourth failed payment you will get a customer.subscription.canceled if that's what you set up in your retry settings in the dashboard. You can detect that it's an automatic cancelation by checking the request key on the event and making sure it's null. In that case you block the UI/account completely for your customer.

For the last 15 days, if I understood your flow correctly, the customer hasn't really been able to use your application since you locked down the UI partially. But if you still want him to pay for that amount, you should store in your database that the customer owes you $X for the 15 days he couldn't use the subscription.

Later on, your customer finally comes back and want to reenable his account. At that point you inform him that the payment flow will restart from today and that you will charge him for the extra 15 days he missed before. To do that, I would use Invoice Items which will allow you to add an extra amount to the first invoice automatically. You would follow those steps:

  1. Create the invoice item for $X for your customer.
  2. Create a new subscription to your plan setting trial_end to "now" in case you have a trial period set by default on your plan.
  3. This will automatically create the first invoice for the plan amount and add the invoice item created before.

Another solution here would be to reopen their latest closed invoice that wasn't paid and use the Pay Invoice API. When this happens you know your customer paid you for a full month now instead of just the 15 days he owed you. In that case you would create a new subscription and set a trial period of 15 days so that he starts paying again after that trial.

I wouldn't use the "Mark as unpaid" option here because you completely lock the customer out of your application. This feature for me is to be used when you let your customer use your application and plan to charge him in a few months for each of the invoices. This would be used for an electricity provider for example who would keep your electricity on for multiple billing cycles and continue to try charging you for each invoice.

The amount your customer really owes you in that case is dependent on the number of retries you allow and the days between each retry. What you can do here is use the created value on the invoice and compare it to the current time when you get the customer.subscription.canceled to determine how much time passed and calculate the proration of how much your customer owes you at that point.

Upvotes: 5

Related Questions