We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
T - fix TypeError: in stripe hook
https://sentry.prod.mozaws.net/operations/mdn-prod/issues/7810756/

```
Resolver404: {'tried': [[<URLPattern '^media/(?:redesign/)?css/(?P<doc>.*)-min.css$'>], [<URLPattern '^media/(?:redesign/)?js/(?P<doc>.*)-min.js$'>], [<URLPattern '^media/(?:redesign/)?img(?P<suffix>.*)$'>], [<URLPattern '^media/(?:redesign/)?css(?P<suffix>.*)$'>], [<URLPattern '^media/(?:redesign/)?js(?P<suffix>.*)$'>], [<URLPattern '^media/(?:redesign/)?fonts(?P<suffix>.*)$'>], [<URLPattern '^media/uploads/demos/(?:.*)$'>], [<URLPattern '(?i)^(?P<one>.*)//(?P<two>.*)//(?P<three>.*)$'>], [<URLPattern '(?i)^(?P<one>.*)//(?P<two>.*)$'>], [<URLPattern '(?i)^samples/canvas-tutorial/2_1_canvas_rect.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/2_2_canvas_moveto.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/2_3_canvas_lineto.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/2_4_canvas_arc.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/2_5_canvas_quadraticcurveto.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/2_6_canvas_beziercurveto.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/3_1_canvas_drawimage.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/3_2_canvas_drawimage.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/3_3_canvas_drawimage.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/3_4_canvas_gallery.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_1_canvas_fillstyle.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_2_canvas_strokestyle.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_3_canvas_globalalpha.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_4_canvas_rgba.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_5_canvas_linewidth.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_6_canvas_linecap.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_7_canvas_linejoin.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_8_canvas_miterlimit.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_9_canvas_lineargradient.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_10_canvas_radialgradient.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_11_canvas_createpattern.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/5_1_canvas_savestate.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/5_2_canvas_translate.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/5_3_canvas_rotate.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/5_4_canvas_scale.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/6_1_canvas_composite.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/6_2_canvas_clipping.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/globalCompositeOperation.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/backdrop.png$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/bg_gallery.png$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/gallery_1.jpg$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/gallery_2.jpg$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/gallery_3.jpg$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/gallery_4.jpg$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/gallery_5.jpg$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/gallery_6.jpg$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/gallery_7.jpg$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/gallery_8.jpg$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/picture_frame.png$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/rhino.jpg$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/wallpaper.png$'>], [<URLPattern '(?i)^samples/domref/mozGetAsFile.html$'>], [<URLPattern '(?i)^samples/raycaster/input.js$'>], [<URLPattern '(?i)^samples/raycaster/Level.js$'>], [<URLPattern '(?i)^samples/raycaster/Player.js$'>], [<URLPattern '(?i)^samples/raycaster/RayCaster.html$'>], [<URLPattern '(?i)^samples/raycaster/RayCaster.js$'>], [<URLPattern '(?i)^samples/raycaster/trace.css$'>], [<URLPattern '(?i)^samples/raycaster/trace.js$'>], [<URLPattern '(?i)^samples/webgl/sample1$'>], [<URLPattern '(?i)^samples/webgl/sample1/index.html$'>], [<URLPattern '(?i)^samples/webgl/sample1/webgl-demo.js$'>], [<UR...
  File "redirect_urls/middleware.py", line 14, in __call__
    resolver_match = self.resolver.resolve(request.path_info)
  File "newrelic/hooks/framework_django.py", line 600, in wrapper
    return _wrapped(*args, **kwargs)
  File "newrelic/hooks/framework_django.py", line 588, in _wrapped
    result = wrapped(path)
  File "django/urls/resolvers.py", line 567, in resolve
    raise Resolver404({'tried': tried, 'path': new_path})

TypeError: 'NoneType' object is not subscriptable
(3 additional frame(s) were not displayed)
...
  File "newrelic/hooks/framework_django.py", line 539, in wrapper
    return wrapped(*args, **kwargs)
  File "django/views/decorators/cache.py", line 44, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "kuma/users/views.py", line 915, in stripe_hooks
    _send_payment_received_email(
  File "kuma/users/views.py", line 944, in _send_payment_received_email
    "next_payment_date": subscription_info["next_payment_at"],

TypeError: 'NoneType' object is not subscriptable
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `kuma/users/stripe_utils.py`
Content:
```
1 from datetime import datetime
2 
3 import stripe
4 from django.conf import settings
5 from django.utils import timezone
6 
7 from kuma.core.urlresolvers import reverse
8 from kuma.wiki.templatetags.jinja_helpers import absolutify
9 
10 from .models import UserSubscription
11 
12 
13 def retrieve_stripe_subscription(customer):
14     for subscription in customer.subscriptions.list().auto_paging_iter():
15         # We have to use array indexing syntax, as stripe uses dicts to
16         # represent its objects (dicts come with an .items method)
17         for item in subscription["items"].auto_paging_iter():
18             if item.plan.id == settings.STRIPE_PLAN_ID:
19                 return subscription
20 
21     return None
22 
23 
24 def retrieve_and_synchronize_subscription_info(user):
25     """For the given user, if it has as 'stripe_customer_id' retrieve the info
26     about the subscription if it's there. All packaged in a way that is
27     practical for the stripe_subscription.html template.
28 
29     Also, whilst doing this check, we also verify that the UserSubscription record
30     for this user is right. Doing that check is a second-layer check in case
31     our webhooks have failed us.
32     """
33     subscription_info = None
34     stripe_customer = get_stripe_customer(user)
35     if stripe_customer:
36         stripe_subscription_info = get_stripe_subscription_info(stripe_customer)
37         if stripe_subscription_info:
38             source = stripe_customer.default_source
39             if source.object == "card":
40                 card = source
41             elif source.object == "source":
42                 card = source.card
43             else:
44                 raise ValueError(
45                     f"unexpected stripe customer default_source of type {source.object!r}"
46                 )
47 
48             subscription_info = {
49                 "id": stripe_subscription_info.id,
50                 "amount": stripe_subscription_info.plan.amount,
51                 "brand": card.brand,
52                 "expires_at": f"{card.exp_month}/{card.exp_year}",
53                 "last4": card.last4,
54                 # Cards that are part of a "source" don't have a zip
55                 "zip": card.get("address_zip", None),
56                 # TODO: Deprecated. Only used in the Edit Profile view
57                 "next_payment_at": datetime.fromtimestamp(
58                     stripe_subscription_info.current_period_end
59                 ),
60             }
61 
62             # To perfect the synchronization, take this opportunity to make sure
63             # we have an up-to-date record of this.
64             UserSubscription.set_active(user, stripe_subscription_info.id)
65         else:
66             # The user has a stripe_customer_id but no active subscription
67             # on the current settings.STRIPE_PLAN_ID! Perhaps it has been canceled
68             # and not updated in our own records.
69             for user_subscription in UserSubscription.objects.filter(
70                 user=user, canceled__isnull=True
71             ):
72                 user_subscription.canceled = timezone.now()
73                 user_subscription.save()
74 
75     return subscription_info
76 
77 
78 def create_stripe_customer_and_subscription_for_user(user, email, stripe_token):
79     customer = (
80         stripe.Customer.retrieve(user.stripe_customer_id)
81         if user.stripe_customer_id
82         else None
83     )
84     if not customer or customer.email != email:
85         customer = stripe.Customer.create(email=email, source=stripe_token)
86         user.stripe_customer_id = customer.id
87         user.save()
88 
89     subscription = retrieve_stripe_subscription(customer)
90     if not subscription:
91         subscription = stripe.Subscription.create(
92             customer=customer.id, items=[{"plan": settings.STRIPE_PLAN_ID}],
93         )
94 
95     UserSubscription.set_active(user, subscription.id)
96 
97 
98 def cancel_stripe_customer_subscriptions(user):
99     """Delete all subscriptions for a Stripe customer."""
100     assert user.stripe_customer_id
101     customer = stripe.Customer.retrieve(user.stripe_customer_id)
102     canceled = []
103     for sub in customer.subscriptions.data:
104         s = stripe.Subscription.retrieve(sub.id)
105         UserSubscription.set_canceled(user, s.id)
106         s.delete()
107         canceled.append(s)
108     return canceled
109 
110 
111 def get_stripe_customer(user):
112     if settings.STRIPE_PLAN_ID and user.stripe_customer_id:
113         return stripe.Customer.retrieve(
114             user.stripe_customer_id, expand=["default_source"]
115         )
116 
117 
118 def get_stripe_subscription_info(stripe_customer):
119     return retrieve_stripe_subscription(stripe_customer)
120 
121 
122 def create_missing_stripe_webhook():
123     url_path = reverse("users.stripe_hooks")
124     url = (
125         "https://" + settings.STRIPE_WEBHOOK_HOSTNAME + url_path
126         if settings.STRIPE_WEBHOOK_HOSTNAME
127         else absolutify(url_path)
128     )
129 
130     # From https://stripe.com/docs/api/webhook_endpoints/create
131     events = (
132         # "Occurs whenever an invoice payment attempt succeeds."
133         "invoice.payment_succeeded",
134         # "Occurs whenever a customer’s subscription ends."
135         # Also, if you go into the Stripe Dashboard, click Billing, Subscriptions,
136         # and find a customer and click the "Cancel subscription" button, this
137         # triggers.
138         "customer.subscription.deleted",
139     )
140 
141     for webhook in stripe.WebhookEndpoint.list().auto_paging_iter():
142         if webhook.url == url and set(events) == set(webhook.enabled_events):
143             return
144 
145     stripe.WebhookEndpoint.create(
146         url=url, enabled_events=events,
147     )
148
```
--- 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/kuma/users/stripe_utils.py b/kuma/users/stripe_utils.py
--- a/kuma/users/stripe_utils.py
+++ b/kuma/users/stripe_utils.py
@@ -11,14 +11,25 @@
 
 
 def retrieve_stripe_subscription(customer):
+    """
+    Returns the first subscription it finds matching the configured stripe
+    plan or, if there's none, just the first it finds.
+    """
+    first_subscription = None
+
     for subscription in customer.subscriptions.list().auto_paging_iter():
+        if first_subscription is None:
+            first_subscription = subscription
+
         # We have to use array indexing syntax, as stripe uses dicts to
         # represent its objects (dicts come with an .items method)
         for item in subscription["items"].auto_paging_iter():
             if item.plan.id == settings.STRIPE_PLAN_ID:
+                # If we find a subscription matching the selected plan we
+                # return that instead of whatever we found first
                 return subscription
 
-    return None
+    return first_subscription
 
 
 def retrieve_and_synchronize_subscription_info(user):

{"golden_diff": "diff --git a/kuma/users/stripe_utils.py b/kuma/users/stripe_utils.py\n--- a/kuma/users/stripe_utils.py\n+++ b/kuma/users/stripe_utils.py\n@@ -11,14 +11,25 @@\n \n \n def retrieve_stripe_subscription(customer):\n+    \"\"\"\n+    Returns the first subscription it finds matching the configured stripe\n+    plan or, if there's none, just the first it finds.\n+    \"\"\"\n+    first_subscription = None\n+\n     for subscription in customer.subscriptions.list().auto_paging_iter():\n+        if first_subscription is None:\n+            first_subscription = subscription\n+\n         # We have to use array indexing syntax, as stripe uses dicts to\n         # represent its objects (dicts come with an .items method)\n         for item in subscription[\"items\"].auto_paging_iter():\n             if item.plan.id == settings.STRIPE_PLAN_ID:\n+                # If we find a subscription matching the selected plan we\n+                # return that instead of whatever we found first\n                 return subscription\n \n-    return None\n+    return first_subscription\n \n \n def retrieve_and_synchronize_subscription_info(user):\n", "issue": "T - fix TypeError: in stripe hook\nhttps://sentry.prod.mozaws.net/operations/mdn-prod/issues/7810756/\n\n```\nResolver404: {'tried': [[<URLPattern '^media/(?:redesign/)?css/(?P<doc>.*)-min.css$'>], [<URLPattern '^media/(?:redesign/)?js/(?P<doc>.*)-min.js$'>], [<URLPattern '^media/(?:redesign/)?img(?P<suffix>.*)$'>], [<URLPattern '^media/(?:redesign/)?css(?P<suffix>.*)$'>], [<URLPattern '^media/(?:redesign/)?js(?P<suffix>.*)$'>], [<URLPattern '^media/(?:redesign/)?fonts(?P<suffix>.*)$'>], [<URLPattern '^media/uploads/demos/(?:.*)$'>], [<URLPattern '(?i)^(?P<one>.*)//(?P<two>.*)//(?P<three>.*)$'>], [<URLPattern '(?i)^(?P<one>.*)//(?P<two>.*)$'>], [<URLPattern '(?i)^samples/canvas-tutorial/2_1_canvas_rect.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/2_2_canvas_moveto.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/2_3_canvas_lineto.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/2_4_canvas_arc.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/2_5_canvas_quadraticcurveto.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/2_6_canvas_beziercurveto.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/3_1_canvas_drawimage.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/3_2_canvas_drawimage.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/3_3_canvas_drawimage.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/3_4_canvas_gallery.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_1_canvas_fillstyle.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_2_canvas_strokestyle.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_3_canvas_globalalpha.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_4_canvas_rgba.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_5_canvas_linewidth.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_6_canvas_linecap.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_7_canvas_linejoin.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_8_canvas_miterlimit.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_9_canvas_lineargradient.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_10_canvas_radialgradient.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/4_11_canvas_createpattern.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/5_1_canvas_savestate.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/5_2_canvas_translate.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/5_3_canvas_rotate.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/5_4_canvas_scale.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/6_1_canvas_composite.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/6_2_canvas_clipping.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/globalCompositeOperation.html$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/backdrop.png$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/bg_gallery.png$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/gallery_1.jpg$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/gallery_2.jpg$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/gallery_3.jpg$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/gallery_4.jpg$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/gallery_5.jpg$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/gallery_6.jpg$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/gallery_7.jpg$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/gallery_8.jpg$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/picture_frame.png$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/rhino.jpg$'>], [<URLPattern '(?i)^samples/canvas-tutorial/images/wallpaper.png$'>], [<URLPattern '(?i)^samples/domref/mozGetAsFile.html$'>], [<URLPattern '(?i)^samples/raycaster/input.js$'>], [<URLPattern '(?i)^samples/raycaster/Level.js$'>], [<URLPattern '(?i)^samples/raycaster/Player.js$'>], [<URLPattern '(?i)^samples/raycaster/RayCaster.html$'>], [<URLPattern '(?i)^samples/raycaster/RayCaster.js$'>], [<URLPattern '(?i)^samples/raycaster/trace.css$'>], [<URLPattern '(?i)^samples/raycaster/trace.js$'>], [<URLPattern '(?i)^samples/webgl/sample1$'>], [<URLPattern '(?i)^samples/webgl/sample1/index.html$'>], [<URLPattern '(?i)^samples/webgl/sample1/webgl-demo.js$'>], [<UR...\n  File \"redirect_urls/middleware.py\", line 14, in __call__\n    resolver_match = self.resolver.resolve(request.path_info)\n  File \"newrelic/hooks/framework_django.py\", line 600, in wrapper\n    return _wrapped(*args, **kwargs)\n  File \"newrelic/hooks/framework_django.py\", line 588, in _wrapped\n    result = wrapped(path)\n  File \"django/urls/resolvers.py\", line 567, in resolve\n    raise Resolver404({'tried': tried, 'path': new_path})\n\nTypeError: 'NoneType' object is not subscriptable\n(3 additional frame(s) were not displayed)\n...\n  File \"newrelic/hooks/framework_django.py\", line 539, in wrapper\n    return wrapped(*args, **kwargs)\n  File \"django/views/decorators/cache.py\", line 44, in _wrapped_view_func\n    response = view_func(request, *args, **kwargs)\n  File \"django/views/decorators/csrf.py\", line 54, in wrapped_view\n    return view_func(*args, **kwargs)\n  File \"kuma/users/views.py\", line 915, in stripe_hooks\n    _send_payment_received_email(\n  File \"kuma/users/views.py\", line 944, in _send_payment_received_email\n    \"next_payment_date\": subscription_info[\"next_payment_at\"],\n\nTypeError: 'NoneType' object is not subscriptable\n```\n", "before_files": [{"content": "from datetime import datetime\n\nimport stripe\nfrom django.conf import settings\nfrom django.utils import timezone\n\nfrom kuma.core.urlresolvers import reverse\nfrom kuma.wiki.templatetags.jinja_helpers import absolutify\n\nfrom .models import UserSubscription\n\n\ndef retrieve_stripe_subscription(customer):\n    for subscription in customer.subscriptions.list().auto_paging_iter():\n        # We have to use array indexing syntax, as stripe uses dicts to\n        # represent its objects (dicts come with an .items method)\n        for item in subscription[\"items\"].auto_paging_iter():\n            if item.plan.id == settings.STRIPE_PLAN_ID:\n                return subscription\n\n    return None\n\n\ndef retrieve_and_synchronize_subscription_info(user):\n    \"\"\"For the given user, if it has as 'stripe_customer_id' retrieve the info\n    about the subscription if it's there. All packaged in a way that is\n    practical for the stripe_subscription.html template.\n\n    Also, whilst doing this check, we also verify that the UserSubscription record\n    for this user is right. Doing that check is a second-layer check in case\n    our webhooks have failed us.\n    \"\"\"\n    subscription_info = None\n    stripe_customer = get_stripe_customer(user)\n    if stripe_customer:\n        stripe_subscription_info = get_stripe_subscription_info(stripe_customer)\n        if stripe_subscription_info:\n            source = stripe_customer.default_source\n            if source.object == \"card\":\n                card = source\n            elif source.object == \"source\":\n                card = source.card\n            else:\n                raise ValueError(\n                    f\"unexpected stripe customer default_source of type {source.object!r}\"\n                )\n\n            subscription_info = {\n                \"id\": stripe_subscription_info.id,\n                \"amount\": stripe_subscription_info.plan.amount,\n                \"brand\": card.brand,\n                \"expires_at\": f\"{card.exp_month}/{card.exp_year}\",\n                \"last4\": card.last4,\n                # Cards that are part of a \"source\" don't have a zip\n                \"zip\": card.get(\"address_zip\", None),\n                # TODO: Deprecated. Only used in the Edit Profile view\n                \"next_payment_at\": datetime.fromtimestamp(\n                    stripe_subscription_info.current_period_end\n                ),\n            }\n\n            # To perfect the synchronization, take this opportunity to make sure\n            # we have an up-to-date record of this.\n            UserSubscription.set_active(user, stripe_subscription_info.id)\n        else:\n            # The user has a stripe_customer_id but no active subscription\n            # on the current settings.STRIPE_PLAN_ID! Perhaps it has been canceled\n            # and not updated in our own records.\n            for user_subscription in UserSubscription.objects.filter(\n                user=user, canceled__isnull=True\n            ):\n                user_subscription.canceled = timezone.now()\n                user_subscription.save()\n\n    return subscription_info\n\n\ndef create_stripe_customer_and_subscription_for_user(user, email, stripe_token):\n    customer = (\n        stripe.Customer.retrieve(user.stripe_customer_id)\n        if user.stripe_customer_id\n        else None\n    )\n    if not customer or customer.email != email:\n        customer = stripe.Customer.create(email=email, source=stripe_token)\n        user.stripe_customer_id = customer.id\n        user.save()\n\n    subscription = retrieve_stripe_subscription(customer)\n    if not subscription:\n        subscription = stripe.Subscription.create(\n            customer=customer.id, items=[{\"plan\": settings.STRIPE_PLAN_ID}],\n        )\n\n    UserSubscription.set_active(user, subscription.id)\n\n\ndef cancel_stripe_customer_subscriptions(user):\n    \"\"\"Delete all subscriptions for a Stripe customer.\"\"\"\n    assert user.stripe_customer_id\n    customer = stripe.Customer.retrieve(user.stripe_customer_id)\n    canceled = []\n    for sub in customer.subscriptions.data:\n        s = stripe.Subscription.retrieve(sub.id)\n        UserSubscription.set_canceled(user, s.id)\n        s.delete()\n        canceled.append(s)\n    return canceled\n\n\ndef get_stripe_customer(user):\n    if settings.STRIPE_PLAN_ID and user.stripe_customer_id:\n        return stripe.Customer.retrieve(\n            user.stripe_customer_id, expand=[\"default_source\"]\n        )\n\n\ndef get_stripe_subscription_info(stripe_customer):\n    return retrieve_stripe_subscription(stripe_customer)\n\n\ndef create_missing_stripe_webhook():\n    url_path = reverse(\"users.stripe_hooks\")\n    url = (\n        \"https://\" + settings.STRIPE_WEBHOOK_HOSTNAME + url_path\n        if settings.STRIPE_WEBHOOK_HOSTNAME\n        else absolutify(url_path)\n    )\n\n    # From https://stripe.com/docs/api/webhook_endpoints/create\n    events = (\n        # \"Occurs whenever an invoice payment attempt succeeds.\"\n        \"invoice.payment_succeeded\",\n        # \"Occurs whenever a customer\u2019s subscription ends.\"\n        # Also, if you go into the Stripe Dashboard, click Billing, Subscriptions,\n        # and find a customer and click the \"Cancel subscription\" button, this\n        # triggers.\n        \"customer.subscription.deleted\",\n    )\n\n    for webhook in stripe.WebhookEndpoint.list().auto_paging_iter():\n        if webhook.url == url and set(events) == set(webhook.enabled_events):\n            return\n\n    stripe.WebhookEndpoint.create(\n        url=url, enabled_events=events,\n    )\n", "path": "kuma/users/stripe_utils.py"}], "after_files": [{"content": "from datetime import datetime\n\nimport stripe\nfrom django.conf import settings\nfrom django.utils import timezone\n\nfrom kuma.core.urlresolvers import reverse\nfrom kuma.wiki.templatetags.jinja_helpers import absolutify\n\nfrom .models import UserSubscription\n\n\ndef retrieve_stripe_subscription(customer):\n    \"\"\"\n    Returns the first subscription it finds matching the configured stripe\n    plan or, if there's none, just the first it finds.\n    \"\"\"\n    first_subscription = None\n\n    for subscription in customer.subscriptions.list().auto_paging_iter():\n        if first_subscription is None:\n            first_subscription = subscription\n\n        # We have to use array indexing syntax, as stripe uses dicts to\n        # represent its objects (dicts come with an .items method)\n        for item in subscription[\"items\"].auto_paging_iter():\n            if item.plan.id == settings.STRIPE_PLAN_ID:\n                # If we find a subscription matching the selected plan we\n                # return that instead of whatever we found first\n                return subscription\n\n    return first_subscription\n\n\ndef retrieve_and_synchronize_subscription_info(user):\n    \"\"\"For the given user, if it has as 'stripe_customer_id' retrieve the info\n    about the subscription if it's there. All packaged in a way that is\n    practical for the stripe_subscription.html template.\n\n    Also, whilst doing this check, we also verify that the UserSubscription record\n    for this user is right. Doing that check is a second-layer check in case\n    our webhooks have failed us.\n    \"\"\"\n    subscription_info = None\n    stripe_customer = get_stripe_customer(user)\n    if stripe_customer:\n        stripe_subscription_info = get_stripe_subscription_info(stripe_customer)\n        if stripe_subscription_info:\n            source = stripe_customer.default_source\n            if source.object == \"card\":\n                card = source\n            elif source.object == \"source\":\n                card = source.card\n            else:\n                raise ValueError(\n                    f\"unexpected stripe customer default_source of type {source.object!r}\"\n                )\n\n            subscription_info = {\n                \"id\": stripe_subscription_info.id,\n                \"amount\": stripe_subscription_info.plan.amount,\n                \"brand\": card.brand,\n                \"expires_at\": f\"{card.exp_month}/{card.exp_year}\",\n                \"last4\": card.last4,\n                # Cards that are part of a \"source\" don't have a zip\n                \"zip\": card.get(\"address_zip\", None),\n                # TODO: Deprecated. Only used in the Edit Profile view\n                \"next_payment_at\": datetime.fromtimestamp(\n                    stripe_subscription_info.current_period_end\n                ),\n            }\n\n            # To perfect the synchronization, take this opportunity to make sure\n            # we have an up-to-date record of this.\n            UserSubscription.set_active(user, stripe_subscription_info.id)\n        else:\n            # The user has a stripe_customer_id but no active subscription\n            # on the current settings.STRIPE_PLAN_ID! Perhaps it has been canceled\n            # and not updated in our own records.\n            for user_subscription in UserSubscription.objects.filter(\n                user=user, canceled__isnull=True\n            ):\n                user_subscription.canceled = timezone.now()\n                user_subscription.save()\n\n    return subscription_info\n\n\ndef create_stripe_customer_and_subscription_for_user(user, email, stripe_token):\n    customer = (\n        stripe.Customer.retrieve(user.stripe_customer_id)\n        if user.stripe_customer_id\n        else None\n    )\n    if not customer or customer.email != email:\n        customer = stripe.Customer.create(email=email, source=stripe_token)\n        user.stripe_customer_id = customer.id\n        user.save()\n\n    subscription = retrieve_stripe_subscription(customer)\n    if not subscription:\n        subscription = stripe.Subscription.create(\n            customer=customer.id, items=[{\"plan\": settings.STRIPE_PLAN_ID}],\n        )\n\n    UserSubscription.set_active(user, subscription.id)\n\n\ndef cancel_stripe_customer_subscriptions(user):\n    \"\"\"Delete all subscriptions for a Stripe customer.\"\"\"\n    assert user.stripe_customer_id\n    customer = stripe.Customer.retrieve(user.stripe_customer_id)\n    canceled = []\n    for sub in customer.subscriptions.data:\n        s = stripe.Subscription.retrieve(sub.id)\n        UserSubscription.set_canceled(user, s.id)\n        s.delete()\n        canceled.append(s)\n    return canceled\n\n\ndef get_stripe_customer(user):\n    if settings.STRIPE_PLAN_ID and user.stripe_customer_id:\n        return stripe.Customer.retrieve(\n            user.stripe_customer_id, expand=[\"default_source\"]\n        )\n\n\ndef get_stripe_subscription_info(stripe_customer):\n    return retrieve_stripe_subscription(stripe_customer)\n\n\ndef create_missing_stripe_webhook():\n    url_path = reverse(\"users.stripe_hooks\")\n    url = (\n        \"https://\" + settings.STRIPE_WEBHOOK_HOSTNAME + url_path\n        if settings.STRIPE_WEBHOOK_HOSTNAME\n        else absolutify(url_path)\n    )\n\n    # From https://stripe.com/docs/api/webhook_endpoints/create\n    events = (\n        # \"Occurs whenever an invoice payment attempt succeeds.\"\n        \"invoice.payment_succeeded\",\n        # \"Occurs whenever a customer\u2019s subscription ends.\"\n        # Also, if you go into the Stripe Dashboard, click Billing, Subscriptions,\n        # and find a customer and click the \"Cancel subscription\" button, this\n        # triggers.\n        \"customer.subscription.deleted\",\n    )\n\n    for webhook in stripe.WebhookEndpoint.list().auto_paging_iter():\n        if webhook.url == url and set(events) == set(webhook.enabled_events):\n            return\n\n    stripe.WebhookEndpoint.create(\n        url=url, enabled_events=events,\n    )\n", "path": "kuma/users/stripe_utils.py"}]}