We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `djstripe/views.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 """
3 .. module:: djstripe.webhooks.
4 
5   :synopsis: dj-stripe - Views related to the djstripe app.
6 
7 .. moduleauthor:: @kavdev, @pydanny, @lskillen, @wahuneke, @dollydagr, @chrissmejia
8 """
9 from __future__ import unicode_literals
10 
11 import json
12 import logging
13 
14 from braces.views import CsrfExemptMixin, FormValidMessageMixin, LoginRequiredMixin, SelectRelatedMixin
15 from django.contrib import messages
16 from django.contrib.auth import logout as auth_logout
17 from django.core.urlresolvers import reverse_lazy, reverse
18 from django.http import HttpResponse
19 from django.http.response import HttpResponseNotFound
20 from django.shortcuts import render, redirect
21 from django.utils.encoding import smart_str
22 from django.views.generic import DetailView, FormView, TemplateView, View
23 from stripe.error import StripeError
24 
25 from . import settings as djstripe_settings
26 from .forms import PlanForm, CancelSubscriptionForm
27 from .mixins import PaymentsContextMixin, SubscriptionMixin
28 from .models import Customer, Event, EventProcessingException, Plan
29 from .sync import sync_subscriber
30 from .webhooks import TEST_EVENT_ID
31 
32 logger = logging.getLogger(__name__)
33 
34 # ============================================================================ #
35 #                                 Account Views                                #
36 # ============================================================================ #
37 
38 
39 class AccountView(LoginRequiredMixin, SelectRelatedMixin, SubscriptionMixin, PaymentsContextMixin, TemplateView):
40     """Shows account details including customer and subscription details."""
41 
42     template_name = "djstripe/account.html"
43 
44 
45 # ============================================================================ #
46 #                                 Billing Views                                #
47 # ============================================================================ #
48 
49 class ChangeCardView(LoginRequiredMixin, PaymentsContextMixin, DetailView):
50     """TODO: Needs to be refactored to leverage forms and context data."""
51 
52     template_name = "djstripe/change_card.html"
53 
54     def get_object(self):
55         """
56         Return a Customer object.
57 
58         Ether returns the Customer object from the current class instance or
59         uses get_or_create.
60         """
61         if hasattr(self, "customer"):
62             return self.customer
63         self.customer, _created = Customer.get_or_create(
64             subscriber=djstripe_settings.subscriber_request_callback(self.request)
65         )
66         return self.customer
67 
68     def post(self, request, *args, **kwargs):
69         """TODO: Raise a validation error when a stripe token isn't passed. Should be resolved when a form is used."""
70         customer = self.get_object()
71         try:
72             send_invoice = not customer.default_source
73             customer.add_card(
74                 request.POST.get("stripe_token")
75             )
76             if send_invoice:
77                 customer.send_invoice()
78             customer.retry_unpaid_invoices()
79         except StripeError as exc:
80             messages.info(request, "Stripe Error")
81             return render(
82                 request,
83                 self.template_name,
84                 {
85                     "customer": self.get_object(),
86                     "stripe_error": str(exc)
87                 }
88             )
89         messages.info(request, "Your card is now updated.")
90         return redirect(self.get_post_success_url())
91 
92     def get_post_success_url(self):
93         """Make it easier to do custom dj-stripe integrations."""
94         return reverse("djstripe:account")
95 
96 
97 class HistoryView(LoginRequiredMixin, SelectRelatedMixin, DetailView):
98     """A view used to return customer history of invoices."""
99 
100     template_name = "djstripe/history.html"
101     model = Customer
102     select_related = ["invoice"]
103 
104     def get_object(self):
105         """Return a Customer object."""
106         customer, _created = Customer.get_or_create(
107             subscriber=djstripe_settings.subscriber_request_callback(self.request)
108         )
109         return customer
110 
111 
112 class SyncHistoryView(CsrfExemptMixin, LoginRequiredMixin, View):
113     """TODO: Needs to be refactored to leverage context data."""
114 
115     template_name = "djstripe/includes/_history_table.html"
116 
117     def post(self, request, *args, **kwargs):
118         """Render the template while injecting extra context."""
119         return render(
120             request,
121             self.template_name,
122             {"customer": sync_subscriber(djstripe_settings.subscriber_request_callback(request))}
123         )
124 
125 
126 # ============================================================================ #
127 #                              Subscription Views                              #
128 # ============================================================================ #
129 
130 class ConfirmFormView(LoginRequiredMixin, FormValidMessageMixin, SubscriptionMixin, FormView):
131     """A view used to confirm customers into a subscription plan."""
132 
133     form_class = PlanForm
134     template_name = "djstripe/confirm_form.html"
135     success_url = reverse_lazy("djstripe:history")
136     form_valid_message = "You are now subscribed!"
137 
138     def get(self, request, *args, **kwargs):
139         """Override ConfirmFormView GET to perform extra validation.
140 
141         - Returns 404 when no plan exists.
142         - Redirects to djstripe:subscribe when customer is already subscribed to this plan.
143         """
144         plan_id = self.kwargs['plan_id']
145 
146         if not Plan.objects.filter(id=plan_id).exists():
147             return HttpResponseNotFound()
148 
149         customer, _created = Customer.get_or_create(
150             subscriber=djstripe_settings.subscriber_request_callback(self.request)
151         )
152 
153         if (customer.subscription and str(customer.subscription.plan.id) == plan_id and
154                 customer.subscription.is_valid()):
155             message = "You already subscribed to this plan"
156             messages.info(request, message, fail_silently=True)
157             return redirect("djstripe:subscribe")
158 
159         return super(ConfirmFormView, self).get(request, *args, **kwargs)
160 
161     def get_context_data(self, *args, **kwargs):
162         """Return ConfirmFormView's context with plan_id."""
163         context = super(ConfirmFormView, self).get_context_data(**kwargs)
164         context['plan'] = Plan.objects.get(id=self.kwargs['plan_id'])
165         return context
166 
167     def post(self, request, *args, **kwargs):
168         """
169         Handle POST requests.
170 
171         Instantiates a form instance with the passed POST variables and
172         then checks for validity.
173         """
174         form_class = self.get_form_class()
175         form = self.get_form(form_class)
176         if form.is_valid():
177             try:
178                 customer, _created = Customer.get_or_create(
179                     subscriber=djstripe_settings.subscriber_request_callback(self.request)
180                 )
181                 customer.add_card(self.request.POST.get("stripe_token"))
182                 customer.subscribe(form.cleaned_data["plan"])
183             except StripeError as exc:
184                 form.add_error(None, str(exc))
185                 return self.form_invalid(form)
186             return self.form_valid(form)
187         else:
188             return self.form_invalid(form)
189 
190 
191 class SubscribeView(LoginRequiredMixin, SubscriptionMixin, TemplateView):
192     """A view to render the subscribe template."""
193 
194     template_name = "djstripe/subscribe.html"
195 
196 
197 class ChangePlanView(LoginRequiredMixin, FormValidMessageMixin, SubscriptionMixin, FormView):
198     """
199     A view used to change a Customers plan.
200 
201     TODO: Work in a trial_days kwarg.
202 
203     Also, this should be combined with ConfirmFormView.
204     """
205 
206     form_class = PlanForm
207     template_name = "djstripe/confirm_form.html"
208     success_url = reverse_lazy("djstripe:history")
209     form_valid_message = "You've just changed your plan!"
210 
211     def post(self, request, *args, **kwargs):
212         """Handle a Customer changing a plan.
213 
214         Handles upgrading a plan as well. Throws an error when Customer is not subscribed to any plan.
215         """
216         form = PlanForm(request.POST)
217 
218         customer, _created = Customer.get_or_create(
219             subscriber=djstripe_settings.subscriber_request_callback(self.request)
220         )
221 
222         if not customer.subscription:
223             form.add_error(None, "You must already be subscribed to a plan before you can change it.")
224             return self.form_invalid(form)
225 
226         if form.is_valid():
227             try:
228                 selected_plan = form.cleaned_data["plan"]
229 
230                 # When a customer upgrades their plan, and DJSTRIPE_PRORATION_POLICY_FOR_UPGRADES is set to True,
231                 # we force the proration of the current plan and use it towards the upgraded plan,
232                 # no matter what DJSTRIPE_PRORATION_POLICY is set to.
233                 if djstripe_settings.PRORATION_POLICY_FOR_UPGRADES:
234                     # Is it an upgrade?
235                     if selected_plan.amount > customer.subscription.plan.amount:
236                         customer.subscription.update(plan=selected_plan, prorate=True)
237                     else:
238                         customer.subscription.update(plan=selected_plan)
239                 else:
240                     customer.subscription.update(plan=selected_plan)
241             except StripeError as exc:
242                 form.add_error(None, str(exc))
243                 return self.form_invalid(form)
244             return self.form_valid(form)
245         else:
246             return self.form_invalid(form)
247 
248 
249 class CancelSubscriptionView(LoginRequiredMixin, SubscriptionMixin, FormView):
250     """A view used to cancel a Customer's subscription."""
251 
252     template_name = "djstripe/cancel_subscription.html"
253     form_class = CancelSubscriptionForm
254     success_url = reverse_lazy("djstripe:account")
255 
256     def form_valid(self, form):
257         """Handle canceling the Customer's subscription."""
258         customer, _created = Customer.get_or_create(
259             subscriber=djstripe_settings.subscriber_request_callback(self.request)
260         )
261         subscription = customer.subscription.cancel()
262 
263         if subscription.status == subscription.STATUS_CANCELED:
264             # If no pro-rate, they get kicked right out.
265             messages.info(self.request, "Your subscription is now cancelled.")
266             # logout the user
267             auth_logout(self.request)
268             return redirect("home")
269         else:
270             # If pro-rate, they get some time to stay.
271             messages.info(self.request, "Your subscription status is now '{status}' until '{period_end}'".format(
272                 status=subscription.status, period_end=subscription.current_period_end)
273             )
274 
275         return super(CancelSubscriptionView, self).form_valid(form)
276 
277 
278 # ============================================================================ #
279 #                                 Web Services                                 #
280 # ============================================================================ #
281 
282 
283 class WebHook(CsrfExemptMixin, View):
284     """A view used to handle webhooks."""
285 
286     def post(self, request, *args, **kwargs):
287         """
288         Create an Event object based on request data.
289 
290         Creates an EventProcessingException if the webhook Event is a duplicate.
291         """
292         body = smart_str(request.body)
293         data = json.loads(body)
294 
295         if data['id'] == TEST_EVENT_ID:
296             logger.info("Test webhook received: {}".format(data['type']))
297             return HttpResponse()
298 
299         if Event.stripe_objects.exists_by_json(data):
300             EventProcessingException.objects.create(
301                 data=data,
302                 message="Duplicate event record",
303                 traceback=""
304             )
305         else:
306             event = Event._create_from_stripe_object(data)
307             event.validate()
308 
309             if djstripe_settings.WEBHOOK_EVENT_CALLBACK:
310                 djstripe_settings.WEBHOOK_EVENT_CALLBACK(event)
311             else:
312                 event.process()
313 
314         return HttpResponse()
315
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
     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 @@
         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                                 #

{"golden_diff": "diff --git a/djstripe/views.py b/djstripe/views.py\n--- a/djstripe/views.py\n+++ b/djstripe/views.py\n@@ -13,12 +13,13 @@\n \n from braces.views import CsrfExemptMixin, FormValidMessageMixin, LoginRequiredMixin, SelectRelatedMixin\n from django.contrib import messages\n-from django.contrib.auth import logout as auth_logout\n+from django.contrib.auth import logout as auth_logout, REDIRECT_FIELD_NAME\n from django.core.urlresolvers import reverse_lazy, reverse\n from django.http import HttpResponse\n from django.http.response import HttpResponseNotFound\n from django.shortcuts import render, redirect\n from django.utils.encoding import smart_str\n+from django.utils.http import is_safe_url\n from django.views.generic import DetailView, FormView, TemplateView, View\n from stripe.error import StripeError\n \n@@ -252,6 +253,24 @@\n     template_name = \"djstripe/cancel_subscription.html\"\n     form_class = CancelSubscriptionForm\n     success_url = reverse_lazy(\"djstripe:account\")\n+    redirect_url = reverse_lazy(\"home\")\n+\n+    # messages\n+    subscription_cancel_message = \"Your subscription is now cancelled.\"\n+    subscription_status_message = \"Your subscription status is now '{status}' until '{period_end}'\"\n+\n+    def get_redirect_url(self):\n+        \"\"\"\n+        Return the URL to redirect to when canceling is successful.\n+        Looks in query string for ?next, ensuring it is on the same domain.\n+        \"\"\"\n+        next = self.request.GET.get(REDIRECT_FIELD_NAME)\n+\n+        # is_safe_url() will ensure we don't redirect to another domain\n+        if next and is_safe_url(next):\n+            return next\n+        else:\n+            return self.redirect_url\n \n     def form_valid(self, form):\n         \"\"\"Handle canceling the Customer's subscription.\"\"\"\n@@ -261,19 +280,24 @@\n         subscription = customer.subscription.cancel()\n \n         if subscription.status == subscription.STATUS_CANCELED:\n-            # If no pro-rate, they get kicked right out.\n-            messages.info(self.request, \"Your subscription is now cancelled.\")\n-            # logout the user\n-            auth_logout(self.request)\n-            return redirect(\"home\")\n+            return self.status_cancel()\n         else:\n             # If pro-rate, they get some time to stay.\n-            messages.info(self.request, \"Your subscription status is now '{status}' until '{period_end}'\".format(\n+            messages.info(self.request, self.subscription_status_message.format(\n                 status=subscription.status, period_end=subscription.current_period_end)\n             )\n \n         return super(CancelSubscriptionView, self).form_valid(form)\n \n+    def status_cancel(self):\n+        \"\"\"Triggered when the subscription is immediately canceled (not pro-rated)\"\"\"\n+        # If no pro-rate, they get kicked right out.\n+        messages.info(self.request, self.subscription_cancel_message)\n+        # logout the user\n+        auth_logout(self.request)\n+        # Redirect to next url\n+        return redirect(self.get_redirect_url())\n+\n \n # ============================================================================ #\n #                                 Web Services                                 #\n", "issue": "Redirect other than 'home'\nWould 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.\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\"\"\"\n.. module:: djstripe.webhooks.\n\n  :synopsis: dj-stripe - Views related to the djstripe app.\n\n.. moduleauthor:: @kavdev, @pydanny, @lskillen, @wahuneke, @dollydagr, @chrissmejia\n\"\"\"\nfrom __future__ import unicode_literals\n\nimport json\nimport logging\n\nfrom braces.views import CsrfExemptMixin, FormValidMessageMixin, LoginRequiredMixin, SelectRelatedMixin\nfrom django.contrib import messages\nfrom django.contrib.auth import logout as auth_logout\nfrom django.core.urlresolvers import reverse_lazy, reverse\nfrom django.http import HttpResponse\nfrom django.http.response import HttpResponseNotFound\nfrom django.shortcuts import render, redirect\nfrom django.utils.encoding import smart_str\nfrom django.views.generic import DetailView, FormView, TemplateView, View\nfrom stripe.error import StripeError\n\nfrom . import settings as djstripe_settings\nfrom .forms import PlanForm, CancelSubscriptionForm\nfrom .mixins import PaymentsContextMixin, SubscriptionMixin\nfrom .models import Customer, Event, EventProcessingException, Plan\nfrom .sync import sync_subscriber\nfrom .webhooks import TEST_EVENT_ID\n\nlogger = logging.getLogger(__name__)\n\n# ============================================================================ #\n#                                 Account Views                                #\n# ============================================================================ #\n\n\nclass AccountView(LoginRequiredMixin, SelectRelatedMixin, SubscriptionMixin, PaymentsContextMixin, TemplateView):\n    \"\"\"Shows account details including customer and subscription details.\"\"\"\n\n    template_name = \"djstripe/account.html\"\n\n\n# ============================================================================ #\n#                                 Billing Views                                #\n# ============================================================================ #\n\nclass ChangeCardView(LoginRequiredMixin, PaymentsContextMixin, DetailView):\n    \"\"\"TODO: Needs to be refactored to leverage forms and context data.\"\"\"\n\n    template_name = \"djstripe/change_card.html\"\n\n    def get_object(self):\n        \"\"\"\n        Return a Customer object.\n\n        Ether returns the Customer object from the current class instance or\n        uses get_or_create.\n        \"\"\"\n        if hasattr(self, \"customer\"):\n            return self.customer\n        self.customer, _created = Customer.get_or_create(\n            subscriber=djstripe_settings.subscriber_request_callback(self.request)\n        )\n        return self.customer\n\n    def post(self, request, *args, **kwargs):\n        \"\"\"TODO: Raise a validation error when a stripe token isn't passed. Should be resolved when a form is used.\"\"\"\n        customer = self.get_object()\n        try:\n            send_invoice = not customer.default_source\n            customer.add_card(\n                request.POST.get(\"stripe_token\")\n            )\n            if send_invoice:\n                customer.send_invoice()\n            customer.retry_unpaid_invoices()\n        except StripeError as exc:\n            messages.info(request, \"Stripe Error\")\n            return render(\n                request,\n                self.template_name,\n                {\n                    \"customer\": self.get_object(),\n                    \"stripe_error\": str(exc)\n                }\n            )\n        messages.info(request, \"Your card is now updated.\")\n        return redirect(self.get_post_success_url())\n\n    def get_post_success_url(self):\n        \"\"\"Make it easier to do custom dj-stripe integrations.\"\"\"\n        return reverse(\"djstripe:account\")\n\n\nclass HistoryView(LoginRequiredMixin, SelectRelatedMixin, DetailView):\n    \"\"\"A view used to return customer history of invoices.\"\"\"\n\n    template_name = \"djstripe/history.html\"\n    model = Customer\n    select_related = [\"invoice\"]\n\n    def get_object(self):\n        \"\"\"Return a Customer object.\"\"\"\n        customer, _created = Customer.get_or_create(\n            subscriber=djstripe_settings.subscriber_request_callback(self.request)\n        )\n        return customer\n\n\nclass SyncHistoryView(CsrfExemptMixin, LoginRequiredMixin, View):\n    \"\"\"TODO: Needs to be refactored to leverage context data.\"\"\"\n\n    template_name = \"djstripe/includes/_history_table.html\"\n\n    def post(self, request, *args, **kwargs):\n        \"\"\"Render the template while injecting extra context.\"\"\"\n        return render(\n            request,\n            self.template_name,\n            {\"customer\": sync_subscriber(djstripe_settings.subscriber_request_callback(request))}\n        )\n\n\n# ============================================================================ #\n#                              Subscription Views                              #\n# ============================================================================ #\n\nclass ConfirmFormView(LoginRequiredMixin, FormValidMessageMixin, SubscriptionMixin, FormView):\n    \"\"\"A view used to confirm customers into a subscription plan.\"\"\"\n\n    form_class = PlanForm\n    template_name = \"djstripe/confirm_form.html\"\n    success_url = reverse_lazy(\"djstripe:history\")\n    form_valid_message = \"You are now subscribed!\"\n\n    def get(self, request, *args, **kwargs):\n        \"\"\"Override ConfirmFormView GET to perform extra validation.\n\n        - Returns 404 when no plan exists.\n        - Redirects to djstripe:subscribe when customer is already subscribed to this plan.\n        \"\"\"\n        plan_id = self.kwargs['plan_id']\n\n        if not Plan.objects.filter(id=plan_id).exists():\n            return HttpResponseNotFound()\n\n        customer, _created = Customer.get_or_create(\n            subscriber=djstripe_settings.subscriber_request_callback(self.request)\n        )\n\n        if (customer.subscription and str(customer.subscription.plan.id) == plan_id and\n                customer.subscription.is_valid()):\n            message = \"You already subscribed to this plan\"\n            messages.info(request, message, fail_silently=True)\n            return redirect(\"djstripe:subscribe\")\n\n        return super(ConfirmFormView, self).get(request, *args, **kwargs)\n\n    def get_context_data(self, *args, **kwargs):\n        \"\"\"Return ConfirmFormView's context with plan_id.\"\"\"\n        context = super(ConfirmFormView, self).get_context_data(**kwargs)\n        context['plan'] = Plan.objects.get(id=self.kwargs['plan_id'])\n        return context\n\n    def post(self, request, *args, **kwargs):\n        \"\"\"\n        Handle POST requests.\n\n        Instantiates a form instance with the passed POST variables and\n        then checks for validity.\n        \"\"\"\n        form_class = self.get_form_class()\n        form = self.get_form(form_class)\n        if form.is_valid():\n            try:\n                customer, _created = Customer.get_or_create(\n                    subscriber=djstripe_settings.subscriber_request_callback(self.request)\n                )\n                customer.add_card(self.request.POST.get(\"stripe_token\"))\n                customer.subscribe(form.cleaned_data[\"plan\"])\n            except StripeError as exc:\n                form.add_error(None, str(exc))\n                return self.form_invalid(form)\n            return self.form_valid(form)\n        else:\n            return self.form_invalid(form)\n\n\nclass SubscribeView(LoginRequiredMixin, SubscriptionMixin, TemplateView):\n    \"\"\"A view to render the subscribe template.\"\"\"\n\n    template_name = \"djstripe/subscribe.html\"\n\n\nclass ChangePlanView(LoginRequiredMixin, FormValidMessageMixin, SubscriptionMixin, FormView):\n    \"\"\"\n    A view used to change a Customers plan.\n\n    TODO: Work in a trial_days kwarg.\n\n    Also, this should be combined with ConfirmFormView.\n    \"\"\"\n\n    form_class = PlanForm\n    template_name = \"djstripe/confirm_form.html\"\n    success_url = reverse_lazy(\"djstripe:history\")\n    form_valid_message = \"You've just changed your plan!\"\n\n    def post(self, request, *args, **kwargs):\n        \"\"\"Handle a Customer changing a plan.\n\n        Handles upgrading a plan as well. Throws an error when Customer is not subscribed to any plan.\n        \"\"\"\n        form = PlanForm(request.POST)\n\n        customer, _created = Customer.get_or_create(\n            subscriber=djstripe_settings.subscriber_request_callback(self.request)\n        )\n\n        if not customer.subscription:\n            form.add_error(None, \"You must already be subscribed to a plan before you can change it.\")\n            return self.form_invalid(form)\n\n        if form.is_valid():\n            try:\n                selected_plan = form.cleaned_data[\"plan\"]\n\n                # When a customer upgrades their plan, and DJSTRIPE_PRORATION_POLICY_FOR_UPGRADES is set to True,\n                # we force the proration of the current plan and use it towards the upgraded plan,\n                # no matter what DJSTRIPE_PRORATION_POLICY is set to.\n                if djstripe_settings.PRORATION_POLICY_FOR_UPGRADES:\n                    # Is it an upgrade?\n                    if selected_plan.amount > customer.subscription.plan.amount:\n                        customer.subscription.update(plan=selected_plan, prorate=True)\n                    else:\n                        customer.subscription.update(plan=selected_plan)\n                else:\n                    customer.subscription.update(plan=selected_plan)\n            except StripeError as exc:\n                form.add_error(None, str(exc))\n                return self.form_invalid(form)\n            return self.form_valid(form)\n        else:\n            return self.form_invalid(form)\n\n\nclass CancelSubscriptionView(LoginRequiredMixin, SubscriptionMixin, FormView):\n    \"\"\"A view used to cancel a Customer's subscription.\"\"\"\n\n    template_name = \"djstripe/cancel_subscription.html\"\n    form_class = CancelSubscriptionForm\n    success_url = reverse_lazy(\"djstripe:account\")\n\n    def form_valid(self, form):\n        \"\"\"Handle canceling the Customer's subscription.\"\"\"\n        customer, _created = Customer.get_or_create(\n            subscriber=djstripe_settings.subscriber_request_callback(self.request)\n        )\n        subscription = customer.subscription.cancel()\n\n        if subscription.status == subscription.STATUS_CANCELED:\n            # If no pro-rate, they get kicked right out.\n            messages.info(self.request, \"Your subscription is now cancelled.\")\n            # logout the user\n            auth_logout(self.request)\n            return redirect(\"home\")\n        else:\n            # If pro-rate, they get some time to stay.\n            messages.info(self.request, \"Your subscription status is now '{status}' until '{period_end}'\".format(\n                status=subscription.status, period_end=subscription.current_period_end)\n            )\n\n        return super(CancelSubscriptionView, self).form_valid(form)\n\n\n# ============================================================================ #\n#                                 Web Services                                 #\n# ============================================================================ #\n\n\nclass WebHook(CsrfExemptMixin, View):\n    \"\"\"A view used to handle webhooks.\"\"\"\n\n    def post(self, request, *args, **kwargs):\n        \"\"\"\n        Create an Event object based on request data.\n\n        Creates an EventProcessingException if the webhook Event is a duplicate.\n        \"\"\"\n        body = smart_str(request.body)\n        data = json.loads(body)\n\n        if data['id'] == TEST_EVENT_ID:\n            logger.info(\"Test webhook received: {}\".format(data['type']))\n            return HttpResponse()\n\n        if Event.stripe_objects.exists_by_json(data):\n            EventProcessingException.objects.create(\n                data=data,\n                message=\"Duplicate event record\",\n                traceback=\"\"\n            )\n        else:\n            event = Event._create_from_stripe_object(data)\n            event.validate()\n\n            if djstripe_settings.WEBHOOK_EVENT_CALLBACK:\n                djstripe_settings.WEBHOOK_EVENT_CALLBACK(event)\n            else:\n                event.process()\n\n        return HttpResponse()\n", "path": "djstripe/views.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\"\"\"\n.. module:: djstripe.webhooks.\n\n  :synopsis: dj-stripe - Views related to the djstripe app.\n\n.. moduleauthor:: @kavdev, @pydanny, @lskillen, @wahuneke, @dollydagr, @chrissmejia\n\"\"\"\nfrom __future__ import unicode_literals\n\nimport json\nimport logging\n\nfrom braces.views import CsrfExemptMixin, FormValidMessageMixin, LoginRequiredMixin, SelectRelatedMixin\nfrom django.contrib import messages\nfrom django.contrib.auth import logout as auth_logout, REDIRECT_FIELD_NAME\nfrom django.core.urlresolvers import reverse_lazy, reverse\nfrom django.http import HttpResponse\nfrom django.http.response import HttpResponseNotFound\nfrom django.shortcuts import render, redirect\nfrom django.utils.encoding import smart_str\nfrom django.utils.http import is_safe_url\nfrom django.views.generic import DetailView, FormView, TemplateView, View\nfrom stripe.error import StripeError\n\nfrom . import settings as djstripe_settings\nfrom .forms import PlanForm, CancelSubscriptionForm\nfrom .mixins import PaymentsContextMixin, SubscriptionMixin\nfrom .models import Customer, Event, EventProcessingException, Plan\nfrom .sync import sync_subscriber\nfrom .webhooks import TEST_EVENT_ID\n\nlogger = logging.getLogger(__name__)\n\n# ============================================================================ #\n#                                 Account Views                                #\n# ============================================================================ #\n\n\nclass AccountView(LoginRequiredMixin, SelectRelatedMixin, SubscriptionMixin, PaymentsContextMixin, TemplateView):\n    \"\"\"Shows account details including customer and subscription details.\"\"\"\n\n    template_name = \"djstripe/account.html\"\n\n\n# ============================================================================ #\n#                                 Billing Views                                #\n# ============================================================================ #\n\nclass ChangeCardView(LoginRequiredMixin, PaymentsContextMixin, DetailView):\n    \"\"\"TODO: Needs to be refactored to leverage forms and context data.\"\"\"\n\n    template_name = \"djstripe/change_card.html\"\n\n    def get_object(self):\n        \"\"\"\n        Return a Customer object.\n\n        Ether returns the Customer object from the current class instance or\n        uses get_or_create.\n        \"\"\"\n        if hasattr(self, \"customer\"):\n            return self.customer\n        self.customer, _created = Customer.get_or_create(\n            subscriber=djstripe_settings.subscriber_request_callback(self.request)\n        )\n        return self.customer\n\n    def post(self, request, *args, **kwargs):\n        \"\"\"TODO: Raise a validation error when a stripe token isn't passed. Should be resolved when a form is used.\"\"\"\n        customer = self.get_object()\n        try:\n            send_invoice = not customer.default_source\n            customer.add_card(\n                request.POST.get(\"stripe_token\")\n            )\n            if send_invoice:\n                customer.send_invoice()\n            customer.retry_unpaid_invoices()\n        except StripeError as exc:\n            messages.info(request, \"Stripe Error\")\n            return render(\n                request,\n                self.template_name,\n                {\n                    \"customer\": self.get_object(),\n                    \"stripe_error\": str(exc)\n                }\n            )\n        messages.info(request, \"Your card is now updated.\")\n        return redirect(self.get_post_success_url())\n\n    def get_post_success_url(self):\n        \"\"\"Make it easier to do custom dj-stripe integrations.\"\"\"\n        return reverse(\"djstripe:account\")\n\n\nclass HistoryView(LoginRequiredMixin, SelectRelatedMixin, DetailView):\n    \"\"\"A view used to return customer history of invoices.\"\"\"\n\n    template_name = \"djstripe/history.html\"\n    model = Customer\n    select_related = [\"invoice\"]\n\n    def get_object(self):\n        \"\"\"Return a Customer object.\"\"\"\n        customer, _created = Customer.get_or_create(\n            subscriber=djstripe_settings.subscriber_request_callback(self.request)\n        )\n        return customer\n\n\nclass SyncHistoryView(CsrfExemptMixin, LoginRequiredMixin, View):\n    \"\"\"TODO: Needs to be refactored to leverage context data.\"\"\"\n\n    template_name = \"djstripe/includes/_history_table.html\"\n\n    def post(self, request, *args, **kwargs):\n        \"\"\"Render the template while injecting extra context.\"\"\"\n        return render(\n            request,\n            self.template_name,\n            {\"customer\": sync_subscriber(djstripe_settings.subscriber_request_callback(request))}\n        )\n\n\n# ============================================================================ #\n#                              Subscription Views                              #\n# ============================================================================ #\n\nclass ConfirmFormView(LoginRequiredMixin, FormValidMessageMixin, SubscriptionMixin, FormView):\n    \"\"\"A view used to confirm customers into a subscription plan.\"\"\"\n\n    form_class = PlanForm\n    template_name = \"djstripe/confirm_form.html\"\n    success_url = reverse_lazy(\"djstripe:history\")\n    form_valid_message = \"You are now subscribed!\"\n\n    def get(self, request, *args, **kwargs):\n        \"\"\"Override ConfirmFormView GET to perform extra validation.\n\n        - Returns 404 when no plan exists.\n        - Redirects to djstripe:subscribe when customer is already subscribed to this plan.\n        \"\"\"\n        plan_id = self.kwargs['plan_id']\n\n        if not Plan.objects.filter(id=plan_id).exists():\n            return HttpResponseNotFound()\n\n        customer, _created = Customer.get_or_create(\n            subscriber=djstripe_settings.subscriber_request_callback(self.request)\n        )\n\n        if (customer.subscription and str(customer.subscription.plan.id) == plan_id and\n                customer.subscription.is_valid()):\n            message = \"You already subscribed to this plan\"\n            messages.info(request, message, fail_silently=True)\n            return redirect(\"djstripe:subscribe\")\n\n        return super(ConfirmFormView, self).get(request, *args, **kwargs)\n\n    def get_context_data(self, *args, **kwargs):\n        \"\"\"Return ConfirmFormView's context with plan_id.\"\"\"\n        context = super(ConfirmFormView, self).get_context_data(**kwargs)\n        context['plan'] = Plan.objects.get(id=self.kwargs['plan_id'])\n        return context\n\n    def post(self, request, *args, **kwargs):\n        \"\"\"\n        Handle POST requests.\n\n        Instantiates a form instance with the passed POST variables and\n        then checks for validity.\n        \"\"\"\n        form_class = self.get_form_class()\n        form = self.get_form(form_class)\n        if form.is_valid():\n            try:\n                customer, _created = Customer.get_or_create(\n                    subscriber=djstripe_settings.subscriber_request_callback(self.request)\n                )\n                customer.add_card(self.request.POST.get(\"stripe_token\"))\n                customer.subscribe(form.cleaned_data[\"plan\"])\n            except StripeError as exc:\n                form.add_error(None, str(exc))\n                return self.form_invalid(form)\n            return self.form_valid(form)\n        else:\n            return self.form_invalid(form)\n\n\nclass SubscribeView(LoginRequiredMixin, SubscriptionMixin, TemplateView):\n    \"\"\"A view to render the subscribe template.\"\"\"\n\n    template_name = \"djstripe/subscribe.html\"\n\n\nclass ChangePlanView(LoginRequiredMixin, FormValidMessageMixin, SubscriptionMixin, FormView):\n    \"\"\"\n    A view used to change a Customers plan.\n\n    TODO: Work in a trial_days kwarg.\n\n    Also, this should be combined with ConfirmFormView.\n    \"\"\"\n\n    form_class = PlanForm\n    template_name = \"djstripe/confirm_form.html\"\n    success_url = reverse_lazy(\"djstripe:history\")\n    form_valid_message = \"You've just changed your plan!\"\n\n    def post(self, request, *args, **kwargs):\n        \"\"\"Handle a Customer changing a plan.\n\n        Handles upgrading a plan as well. Throws an error when Customer is not subscribed to any plan.\n        \"\"\"\n        form = PlanForm(request.POST)\n\n        customer, _created = Customer.get_or_create(\n            subscriber=djstripe_settings.subscriber_request_callback(self.request)\n        )\n\n        if not customer.subscription:\n            form.add_error(None, \"You must already be subscribed to a plan before you can change it.\")\n            return self.form_invalid(form)\n\n        if form.is_valid():\n            try:\n                selected_plan = form.cleaned_data[\"plan\"]\n\n                # When a customer upgrades their plan, and DJSTRIPE_PRORATION_POLICY_FOR_UPGRADES is set to True,\n                # we force the proration of the current plan and use it towards the upgraded plan,\n                # no matter what DJSTRIPE_PRORATION_POLICY is set to.\n                if djstripe_settings.PRORATION_POLICY_FOR_UPGRADES:\n                    # Is it an upgrade?\n                    if selected_plan.amount > customer.subscription.plan.amount:\n                        customer.subscription.update(plan=selected_plan, prorate=True)\n                    else:\n                        customer.subscription.update(plan=selected_plan)\n                else:\n                    customer.subscription.update(plan=selected_plan)\n            except StripeError as exc:\n                form.add_error(None, str(exc))\n                return self.form_invalid(form)\n            return self.form_valid(form)\n        else:\n            return self.form_invalid(form)\n\n\nclass CancelSubscriptionView(LoginRequiredMixin, SubscriptionMixin, FormView):\n    \"\"\"A view used to cancel a Customer's subscription.\"\"\"\n\n    template_name = \"djstripe/cancel_subscription.html\"\n    form_class = CancelSubscriptionForm\n    success_url = reverse_lazy(\"djstripe:account\")\n    redirect_url = reverse_lazy(\"home\")\n\n    # messages\n    subscription_cancel_message = \"Your subscription is now cancelled.\"\n    subscription_status_message = \"Your subscription status is now '{status}' until '{period_end}'\"\n\n    def get_redirect_url(self):\n        \"\"\"\n        Return the URL to redirect to when canceling is successful.\n        Looks in query string for ?next, ensuring it is on the same domain.\n        \"\"\"\n        next = self.request.GET.get(REDIRECT_FIELD_NAME)\n\n        # is_safe_url() will ensure we don't redirect to another domain\n        if next and is_safe_url(next):\n            return next\n        else:\n            return self.redirect_url\n\n    def form_valid(self, form):\n        \"\"\"Handle canceling the Customer's subscription.\"\"\"\n        customer, _created = Customer.get_or_create(\n            subscriber=djstripe_settings.subscriber_request_callback(self.request)\n        )\n        subscription = customer.subscription.cancel()\n\n        if subscription.status == subscription.STATUS_CANCELED:\n            return self.status_cancel()\n        else:\n            # If pro-rate, they get some time to stay.\n            messages.info(self.request, self.subscription_status_message.format(\n                status=subscription.status, period_end=subscription.current_period_end)\n            )\n\n        return super(CancelSubscriptionView, self).form_valid(form)\n\n    def status_cancel(self):\n        \"\"\"Triggered when the subscription is immediately canceled (not pro-rated)\"\"\"\n        # If no pro-rate, they get kicked right out.\n        messages.info(self.request, self.subscription_cancel_message)\n        # logout the user\n        auth_logout(self.request)\n        # Redirect to next url\n        return redirect(self.get_redirect_url())\n\n\n# ============================================================================ #\n#                                 Web Services                                 #\n# ============================================================================ #\n\n\nclass WebHook(CsrfExemptMixin, View):\n    \"\"\"A view used to handle webhooks.\"\"\"\n\n    def post(self, request, *args, **kwargs):\n        \"\"\"\n        Create an Event object based on request data.\n\n        Creates an EventProcessingException if the webhook Event is a duplicate.\n        \"\"\"\n        body = smart_str(request.body)\n        data = json.loads(body)\n\n        if data['id'] == TEST_EVENT_ID:\n            logger.info(\"Test webhook received: {}\".format(data['type']))\n            return HttpResponse()\n\n        if Event.stripe_objects.exists_by_json(data):\n            EventProcessingException.objects.create(\n                data=data,\n                message=\"Duplicate event record\",\n                traceback=\"\"\n            )\n        else:\n            event = Event._create_from_stripe_object(data)\n            event.validate()\n\n            if djstripe_settings.WEBHOOK_EVENT_CALLBACK:\n                djstripe_settings.WEBHOOK_EVENT_CALLBACK(event)\n            else:\n                event.process()\n\n        return HttpResponse()\n", "path": "djstripe/views.py"}]}