Repository: dj-stripe/dj-stripe

Issue:
Redirect other than 'home'
Would love if you'd add a settings variable to define what the "cancel plan" redirect would be. I dont have a URL with the name 'home', so this causes an error. Maybe I'm missing a way to change this.



Discussion:
Currently, the only way to change the redirect is to add a url with the name `home` or override the cancel plan view and change the success redirect url.

The redirect url setting is a great idea. I'll add it to the list for the next round of updates.

Until then, I suggest creating a url with the name `home` that uses `RedirectView` to redirect the request to the url you want. It's not very efficient, but it should get you by in the meantime.


Resolving patch:
diff --git a/djstripe/views.py b/djstripe/views.py
--- a/djstripe/views.py
+++ b/djstripe/views.py
@@ -13,12 +13,13 @@
 
 from braces.views import CsrfExemptMixin, FormValidMessageMixin, LoginRequiredMixin, SelectRelatedMixin
 from django.contrib import messages
-from django.contrib.auth import logout as auth_logout
+from django.contrib.auth import logout as auth_logout, REDIRECT_FIELD_NAME
 from django.core.urlresolvers import reverse_lazy, reverse
 from django.http import HttpResponse
 from django.http.response import HttpResponseNotFound
 from django.shortcuts import render, redirect
 from django.utils.encoding import smart_str
+from django.utils.http import is_safe_url
 from django.views.generic import DetailView, FormView, TemplateView, View
 from stripe.error import StripeError
 
@@ -252,6 +253,24 @@ class CancelSubscriptionView(LoginRequiredMixin, SubscriptionMixin, FormView):
     template_name = "djstripe/cancel_subscription.html"
     form_class = CancelSubscriptionForm
     success_url = reverse_lazy("djstripe:account")
+    redirect_url = reverse_lazy("home")
+
+    # messages
+    subscription_cancel_message = "Your subscription is now cancelled."
+    subscription_status_message = "Your subscription status is now '{status}' until '{period_end}'"
+
+    def get_redirect_url(self):
+        """
+        Return the URL to redirect to when canceling is successful.
+        Looks in query string for ?next, ensuring it is on the same domain.
+        """
+        next = self.request.GET.get(REDIRECT_FIELD_NAME)
+
+        # is_safe_url() will ensure we don't redirect to another domain
+        if next and is_safe_url(next):
+            return next
+        else:
+            return self.redirect_url
 
     def form_valid(self, form):
         """Handle canceling the Customer's subscription."""
@@ -261,19 +280,24 @@ def form_valid(self, form):
         subscription = customer.subscription.cancel()
 
         if subscription.status == subscription.STATUS_CANCELED:
-            # If no pro-rate, they get kicked right out.
-            messages.info(self.request, "Your subscription is now cancelled.")
-            # logout the user
-            auth_logout(self.request)
-            return redirect("home")
+            return self.status_cancel()
         else:
             # If pro-rate, they get some time to stay.
-            messages.info(self.request, "Your subscription status is now '{status}' until '{period_end}'".format(
+            messages.info(self.request, self.subscription_status_message.format(
                 status=subscription.status, period_end=subscription.current_period_end)
             )
 
         return super(CancelSubscriptionView, self).form_valid(form)
 
+    def status_cancel(self):
+        """Triggered when the subscription is immediately canceled (not pro-rated)"""
+        # If no pro-rate, they get kicked right out.
+        messages.info(self.request, self.subscription_cancel_message)
+        # logout the user
+        auth_logout(self.request)
+        # Redirect to next url
+        return redirect(self.get_redirect_url())
+
 
 # ============================================================================ #
 #                                 Web Services                                 #


Test patch:
diff --git a/tests/test_views.py b/tests/test_views.py
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -420,3 +420,20 @@ def test_cancel_no_proration(self, cancel_subscription_mock, logout_mock):
         self.assertEqual(response.status_code, 302)
 
         self.assertTrue(logout_mock.called)
+
+    @patch("djstripe.views.auth_logout", autospec=True)
+    @patch("djstripe.models.Subscription.cancel")
+    def test_cancel_no_proration_next_url(self, cancel_subscription_mock, logout_mock):
+        Customer.objects.create(subscriber=self.user, stripe_id=FAKE_CUSTOMER["id"], currency="usd")
+
+        fake_subscription = deepcopy(FAKE_SUBSCRIPTION)
+        fake_subscription.update({"status": Subscription.STATUS_CANCELED})
+        cancel_subscription_mock.return_value = Subscription.sync_from_stripe_data(fake_subscription)
+
+        response = self.client.post(self.url + "?next=/test")
+
+        cancel_subscription_mock.assert_called_once_with()
+        self.assertEqual(response.status_code, 302)
+        self.assertEqual(response.url, "/test")
+
+        self.assertTrue(logout_mock.called)