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.
Reactivate/Un-cancel my Subscription!
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
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:
// ... 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.
// ... 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()
:
// ... 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()
:
// ... 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()
:
// ... 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
:
// ... 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()
:
// ... 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.
Hello, for me, the reactivateSubscription() doesn't work. There is no changements on Stripe. So I follow this link (actual Stripe version) : https://stripe.com/docs/billing/subscriptions/canceling-pausing#reactivating-canceled-subscriptions
And now it works with :