This course is archived!

This tutorial uses an older version of Symfony of the stripe-php SDK. The majority of the concepts are still valid, though there *are* differences. We've done our best to add notes & comments that describe these changes.

Buy Access to Course
09.

Reactivate/Un-cancel my Subscription!

Share this awesome video!

|

Keep on Learning!

So, if someone cancels, they can't un-cancel. And that's a bummer!

In the Stripe API docs, under the "canceling" section, there's actually a spot about reactivating canceled subscriptions, and it's really interesting! It says that if you use the at_period_end method of canceling, and the subscription has not yet reached the period end, then reactivating is easy: just set the subscription's plan to the same plan ID that it had originally. Internally, Stripe knows that means I want to not cancel the subscription anymore.

Route and Controller Setup

Let's hook it up! We're going to need a new endpoint that reactivates a subscription. In ProfileController, add a new public function reactivateSubscriptionAction(). Give it a route set to /profile/subscription/reactivate and a name: account_subscription_reactivate:

// ... lines 1 - 11
class ProfileController extends BaseController
{
// ... lines 14 - 41
/**
* @Route("/profile/subscription/reactivate", name="account_subscription_reactivate")
*/
public function reactivateSubscriptionAction()
{
// ... lines 47 - 55
}
}

Good start! With this in place, copy the route name, open account.html.twig and go up to the "TODO" we added a few minutes ago. Paste the route, just to stash it somewhere, then copy the entire cancel form and put it here. Update the form action with the new route name, change the text, and use btn-success to make this look like a really happy thing:

// ... lines 1 - 2
{% block body %}
<div class="nav-space">
<div class="container">
<div class="row">
<div class="col-xs-6">
<h1>
My Account
{% if app.user.hasActiveSubscription %}
{% if app.user.subscription.isCancelled %}
<form action="{{ path('account_subscription_reactivate') }}" method="POST" class="pull-right">
<button type="submit" class="btn btn-success btn-xs">Reactivate Subscription</button>
</form>
{% else %}
<form action="{{ path('account_subscription_cancel') }}" method="POST" class="pull-right">
<button type="submit" class="btn btn-danger btn-xs">Cancel Subscription</button>
</form>
{% endif %}
{% endif %}
</h1>
// ... lines 23 - 63
</div>
// ... lines 65 - 67
</div>
</div>
</div>
{% endblock %}
// ... lines 72 - 73

Refresh and enjoy the nice, new Reactivate Subscription button. Beautiful!

Expired Subscriptions Cannot be Reactivated

Let's get to work in the controller. Like everything, this will have two parts. First, we need to reactivate the subscription in Stripe and second, we need to update our database. For the first part, fetch the trusty StripeClient service object with $stripeClient = $this->get('stripe_client'):

// ... lines 1 - 11
class ProfileController extends BaseController
{
// ... lines 14 - 44
public function reactivateSubscriptionAction()
{
$stripeClient = $this->get('stripe_client');
// ... lines 48 - 55
}
}

Next, open that class. Add a new public function reactivateSubscription(). It will need a User argument whose subscription we should reactivate:

105 lines | src/AppBundle/StripeClient.php
// ... lines 1 - 8
class StripeClient
{
// ... lines 11 - 88
public function reactivateSubscription(User $user)
{
// ... lines 91 - 102
}
}

As the Stripe docs mentioned, we can only reactivate a subscription that has not been fully canceled. If today is beyond the period end, then the user will need to create an entirely new subscription. That's why we only show the button in our template during this period.

But just in case, add an "if" statement: if !$user->hasActiveSubscription(), then we'll throw a new exception with the text:

Subscriptions can only be reactivated if the subscription has not actually ended.

105 lines | src/AppBundle/StripeClient.php
// ... lines 1 - 8
class StripeClient
{
// ... lines 11 - 88
public function reactivateSubscription(User $user)
{
if (!$user->hasActiveSubscription()) {
throw new \LogicException('Subscriptions can only be reactivated if the subscription has not actually ended yet');
}
// ... lines 94 - 102
}
}

Nothing should hit that code, but now we'll know if something does.

Reactivate in Stripe

To reactivate the Subscription, we first need to fetch it. In the Stripe API docs, find "Retrieve a Subscription." Every object can be fetched using the same retrieve method. Copy this. Then, add, $subscription = and paste. Replace the subscription ID with $user->getSubscription()->getStripeSubscriptionId():

105 lines | src/AppBundle/StripeClient.php
// ... lines 1 - 90
if (!$user->hasActiveSubscription()) {
throw new \LogicException('Subscriptions can only be reactivated if the subscription has not actually ended yet');
}
$subscription = \Stripe\Subscription::retrieve(
$user->getSubscription()->getStripeSubscriptionId()
);
// ... lines 98 - 105

And remember, if any API call to Stripe fails - like because this is an invalid subscription ID - the library will throw an exception. So we don't need to add extra code to check if that subscription was found.

Finally, reactivate the subscription by setting its plan property equal to the original plan ID, which is $user->getSubscription()->getStripePlanId():

105 lines | src/AppBundle/StripeClient.php
// ... lines 1 - 90
if (!$user->hasActiveSubscription()) {
throw new \LogicException('Subscriptions can only be reactivated if the subscription has not actually ended yet');
}
$subscription = \Stripe\Subscription::retrieve(
$user->getSubscription()->getStripeSubscriptionId()
);
// this triggers the refresh of the subscription!
$subscription->plan = $user->getSubscription()->getStripePlanId();
// ... lines 100 - 105

Then, send the details to Stripe with $subscription->save():

105 lines | src/AppBundle/StripeClient.php
// ... lines 1 - 90
if (!$user->hasActiveSubscription()) {
throw new \LogicException('Subscriptions can only be reactivated if the subscription has not actually ended yet');
}
$subscription = \Stripe\Subscription::retrieve(
$user->getSubscription()->getStripeSubscriptionId()
);
// this triggers the refresh of the subscription!
$subscription->plan = $user->getSubscription()->getStripePlanId();
$subscription->save();
// ... lines 101 - 105

And just in case, return the $subscription:

105 lines | src/AppBundle/StripeClient.php
// ... lines 1 - 8
class StripeClient
{
// ... lines 11 - 88
public function reactivateSubscription(User $user)
{
if (!$user->hasActiveSubscription()) {
throw new \LogicException('Subscriptions can only be reactivated if the subscription has not actually ended yet');
}
$subscription = \Stripe\Subscription::retrieve(
$user->getSubscription()->getStripeSubscriptionId()
);
// this triggers the refresh of the subscription!
$subscription->plan = $user->getSubscription()->getStripePlanId();
$subscription->save();
return $subscription;
}
}

Love it! Back in ProfileController, reactivate the subscription with, $stripeClient->reactivateSubscription($this->getUser()):

// ... lines 1 - 11
class ProfileController extends BaseController
{
// ... lines 14 - 44
public function reactivateSubscriptionAction()
{
$stripeClient = $this->get('stripe_client');
$stripeSubscription = $stripeClient->reactivateSubscription($this->getUser());
// ... lines 49 - 55
}
}

And we are done on the Stripe side.

Updating our Database

The other thing we need to worry about - which turns out to be really easy - is to update our database so that this, once again, looks like an active subscription. It's easy, because we've already done the work for this. Check out SubscriptionHelper: we have a method called addSubscriptionToUser(), which is normally used right after the user originally buys a new subscription:

// ... lines 1 - 8
class SubscriptionHelper
{
// ... lines 11 - 45
public function addSubscriptionToUser(\Stripe\Subscription $stripeSubscription, User $user)
{
$subscription = $user->getSubscription();
if (!$subscription) {
$subscription = new Subscription();
$subscription->setUser($user);
}
$periodEnd = \DateTime::createFromFormat('U', $stripeSubscription->current_period_end);
$subscription->activateSubscription(
$stripeSubscription->plan->id,
$stripeSubscription->id,
$periodEnd
);
$this->em->persist($subscription);
$this->em->flush($subscription);
}
// ... lines 64 - 72
}

But we can could also call this after reactivating. In reality, this method simply ensures that the Subscription row in the table is up-to-date with the latest stripePlanId, stripeSubscriptionId, periodEnd and endsAt:

// ... lines 1 - 8
class SubscriptionHelper
{
// ... lines 11 - 45
public function addSubscriptionToUser(\Stripe\Subscription $stripeSubscription, User $user)
{
// ... lines 48 - 54
$subscription->activateSubscription(
$stripeSubscription->plan->id,
$stripeSubscription->id,
$periodEnd
);
// ... lines 60 - 62
}
// ... lines 64 - 72
}

These last two are the most important: because they changed when we deactivated the subscription. So by calling activateSubscription():

125 lines | src/AppBundle/Entity/Subscription.php
// ... lines 1 - 10
class Subscription
{
// ... lines 13 - 94
public function activateSubscription($stripePlanId, $stripeSubscriptionId, \DateTime $periodEnd)
{
$this->stripePlanId = $stripePlanId;
$this->stripeSubscriptionId = $stripeSubscriptionId;
$this->billingPeriodEndsAt = $periodEnd;
$this->endsAt = null;
}
// ... lines 102 - 123
}

All of that will be reversed, and the subscription will be alive!

Let's do it! In ProfileController, add a $stripeSubscription = in front of the $stripeClient call. Below that, use $this->get('subscription_helper')->addSubscriptionToUser() and pass it $stripeSubscription and the current user:

// ... lines 1 - 11
class ProfileController extends BaseController
{
// ... lines 14 - 44
public function reactivateSubscriptionAction()
{
$stripeClient = $this->get('stripe_client');
$stripeSubscription = $stripeClient->reactivateSubscription($this->getUser());
$this->get('subscription_helper')
->addSubscriptionToUser($stripeSubscription, $this->getUser());
// ... lines 52 - 55
}
}

And that is everything!

Give your user a happy flash message and redirect back to the profile page:

// ... lines 1 - 11
class ProfileController extends BaseController
{
// ... lines 14 - 44
public function reactivateSubscriptionAction()
{
$stripeClient = $this->get('stripe_client');
$stripeSubscription = $stripeClient->reactivateSubscription($this->getUser());
$this->get('subscription_helper')
->addSubscriptionToUser($stripeSubscription, $this->getUser());
$this->addFlash('success', 'Welcome back!');
return $this->redirectToRoute('profile_account');
}
}

I think we're ready to try this! Go back and refresh the profile. Press reactivate and... our "cancel subscription" button is back, "active" is back, "next billing period" is back and "credit card" is back. In Stripe, the customer's most recent subscription also became active again. Oh man, this is kind of fun to play with: cancel, reactivate, cancel, reactivate. The system is solid.